package org.mozilla.javascript.optimizer;

import static org.mozilla.classfile.ClassFileWriter.ACC_PRIVATE;
import static org.mozilla.classfile.ClassFileWriter.ACC_PUBLIC;
import static org.mozilla.classfile.ClassFileWriter.ACC_STATIC;

import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.mozilla.classfile.ByteCode;
import org.mozilla.classfile.ClassFileWriter;
import org.mozilla.javascript.CompilerEnvirons;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.Kit;
import org.mozilla.javascript.NativeGenerator;
import org.mozilla.javascript.Node;
import org.mozilla.javascript.ScriptRuntime;
import org.mozilla.javascript.Token;
import org.mozilla.javascript.ast.FunctionNode;
import org.mozilla.javascript.ast.Jump;
import org.mozilla.javascript.ast.ScriptNode;

class BodyCodegen {
    void generateBodyCode() {
        isGenerator = Codegen.isGenerator(scriptOrFn);

        // generate the body of the current function or script object
        initBodyGeneration();

        if (isGenerator) {
            // All functions in the generated bytecode have a unique name. Every
            // generator has a unique prefix followed by _gen
            cfw.startMethod(
                    codegen.getBodyMethodName(scriptOrFn) + "_gen",
                    Codegen.GENERATOR_METHOD_SIGNATURE,
                    (short) (ACC_STATIC | ACC_PUBLIC));
        } else {
            cfw.startMethod(
                    codegen.getBodyMethodName(scriptOrFn),
                    codegen.getBodyMethodSignature(scriptOrFn),
                    (short) (ACC_STATIC | ACC_PUBLIC));
        }

        generatePrologue();
        Node treeTop;
        if (fnCurrent != null) {
            treeTop = scriptOrFn.getLastChild();
        } else {
            treeTop = scriptOrFn;
        }
        generateStatement(treeTop);
        generateEpilogue();

        cfw.stopMethod((short) (localsMax + 1));

        if (isGenerator) {
            // generate the user visible method which when invoked will
            // return a generator object
            generateGenerator();
        }

        if (literals != null) {
            // literals list may grow while we're looping
            for (int i = 0; i < literals.size(); i++) {
                Node node = literals.get(i);
                int type = node.getType();
                switch (type) {
                    case Token.OBJECTLIT:
                        generateObjectLiteralFactory(node, i + 1);
                        break;
                    case Token.ARRAYLIT:
                        generateArrayLiteralFactory(node, i + 1);
                        break;
                    default:
                        Kit.codeBug(Token.typeToName(type));
                }
            }
        }
    }

    // This creates a user-facing function that returns a NativeGenerator
    // object.
    private void generateGenerator() {
        cfw.startMethod(
                codegen.getBodyMethodName(scriptOrFn),
                codegen.getBodyMethodSignature(scriptOrFn),
                (short) (ACC_STATIC | ACC_PUBLIC));

        initBodyGeneration();
        argsLocal = firstFreeLocal++;
        localsMax = firstFreeLocal;

        // get top level scope
        if (fnCurrent != null) {
            // Unless we're in a direct call use the enclosing scope
            // of the function as our variable object.
            cfw.addALoad(funObjLocal);
            cfw.addInvoke(
                    ByteCode.INVOKEINTERFACE,
                    "org/mozilla/javascript/Function",
                    "getDeclarationScope",
                    "()Lorg/mozilla/javascript/Scriptable;");
            cfw.addAStore(variableObjectLocal);
        }

        // generators are forced to have an activation record
        cfw.addALoad(funObjLocal);
        cfw.addALoad(contextLocal);
        cfw.addALoad(variableObjectLocal);
        cfw.addALoad(argsLocal);
        cfw.addPush(scriptOrFn.isInStrictMode());
        cfw.addPush(scriptOrFn.hasRestParameter());
        cfw.addPush(
                !(scriptOrFn instanceof FunctionNode)
                        || ((FunctionNode) scriptOrFn).requiresArgumentObject());
        addScriptRuntimeInvoke(
                "createFunctionActivation",
                "(Lorg/mozilla/javascript/JSFunction;"
                        + "Lorg/mozilla/javascript/Context;"
                        + "Lorg/mozilla/javascript/Scriptable;"
                        + "[Ljava/lang/Object;"
                        + "Z"
                        + "Z"
                        + "Z"
                        + ")Lorg/mozilla/javascript/Scriptable;");
        cfw.addAStore(variableObjectLocal);

        generateNestedFunctionInits();

        // create the NativeGenerator object that we return
        cfw.addALoad(funObjLocal);
        cfw.addALoad(variableObjectLocal);
        cfw.addALoad(thisObjLocal);
        cfw.addLoadConstant(maxLocals);
        cfw.addLoadConstant(maxStack);
        addOptRuntimeInvoke(
                "createNativeGenerator",
                "(Lorg/mozilla/javascript/JSFunction;"
                        + "Lorg/mozilla/javascript/Scriptable;"
                        + "Lorg/mozilla/javascript/Scriptable;II"
                        + ")Lorg/mozilla/javascript/Scriptable;");

        cfw.add(ByteCode.ARETURN);
        cfw.stopMethod((short) (localsMax + 1));
    }

    private void generateNestedFunctionInits() {
        int functionCount = scriptOrFn.getFunctionCount();
        for (int i = 0; i != functionCount; i++) {
            OptFunctionNode ofn = OptFunctionNode.get(scriptOrFn, i);
            if (ofn.fnode.getFunctionType() == FunctionNode.FUNCTION_STATEMENT) {
                visitFunction(ofn, FunctionNode.FUNCTION_STATEMENT);
            }
        }
    }

    private void initBodyGeneration() {
        varRegisters = null;
        if (scriptOrFn.getType() == Token.FUNCTION) {
            fnCurrent = OptFunctionNode.get(scriptOrFn);
            hasVarsInRegs = !fnCurrent.fnode.requiresActivation();
            if (hasVarsInRegs) {
                int n = fnCurrent.fnode.getParamAndVarCount();
                if (n != 0) {
                    varRegisters = new int[n];
                }
            }
            inDirectCallFunction = fnCurrent.isTargetOfDirectCall();
            if (inDirectCallFunction && !hasVarsInRegs) Codegen.badTree();
        } else {
            fnCurrent = null;
            hasVarsInRegs = false;
            inDirectCallFunction = false;
        }

        locals = new int[MAX_LOCALS];

        contextLocal = 0;
        funObjLocal = 1;

        newTargetLocal = 2;
        variableObjectLocal = 3;
        thisObjLocal = 4;
        localsMax = 5; // number of parms + "this"
        firstFreeLocal = 5;

        popvLocal = -1;
        argsLocal = -1;
        itsZeroArgArray = -1;
        itsOneArgArray = -1;
        epilogueLabel = -1;
        enterAreaStartLabel = -1;
        generatorStateLocal = -1;
        savedHomeObjectLocal = -1;
    }

    /** Generate the prologue for a function or script. */
    private void generatePrologue() {
        if (inDirectCallFunction) {
            int directParameterCount = scriptOrFn.getParamCount();
            // 0 is reserved for function Object 'this'
            // 1 is reserved for context
            // 2 is reserved for parentScope
            // 3 is reserved for script 'this'
            if (firstFreeLocal != 5) Kit.codeBug();
            for (int i = 0; i != directParameterCount; ++i) {
                varRegisters[i] = firstFreeLocal;
                // 3 is 1 for Object parm and 2 for double parm
                firstFreeLocal += 3;
            }
            if (!fnCurrent.getParameterNumberContext()) {
                // make sure that all parameters are objects
                itsForcedObjectParameters = true;
                for (int i = 0; i != directParameterCount; ++i) {
                    int reg = varRegisters[i];
                    cfw.addALoad(reg);
                    cfw.add(ByteCode.GETSTATIC, "java/lang/Void", "TYPE", "Ljava/lang/Class;");
                    int isObjectLabel = cfw.acquireLabel();
                    cfw.add(ByteCode.IF_ACMPNE, isObjectLabel);
                    cfw.addDLoad(reg + 1);
                    addDoubleWrap();
                    cfw.addAStore(reg);
                    cfw.markLabel(isObjectLabel);
                }
            }
        }

        if (fnCurrent != null) {
            // Use the enclosing scope of the function as our variable object.
            cfw.addALoad(funObjLocal);
            cfw.addInvoke(
                    ByteCode.INVOKEINTERFACE,
                    "org/mozilla/javascript/Function",
                    "getDeclarationScope",
                    "()Lorg/mozilla/javascript/Scriptable;");
            cfw.addAStore(variableObjectLocal);
        }

        // reserve 'args[]'
        argsLocal = firstFreeLocal++;
        localsMax = firstFreeLocal;

        // Generate Generator specific prelude
        if (isGenerator) {

            // reserve 'args[]'
            operationLocal = firstFreeLocal++;
            localsMax = firstFreeLocal;
            cfw.addILoad(thisObjLocal);
            cfw.addIStore(operationLocal);

            // Local 2 is a reference to a GeneratorState object. The rest
            // of codegen expects local 3 to be a reference to the thisObj.
            // So move the value in local 3 to generatorStateLocal, and load
            // the saved thisObj from the GeneratorState object.
            generatorStateLocal = newTargetLocal;
            localsMax = firstFreeLocal;
            cfw.addALoad(generatorStateLocal);
            cfw.add(ByteCode.CHECKCAST, OptRuntime.GeneratorState.CLASS_NAME);
            cfw.add(ByteCode.DUP);
            cfw.addAStore(generatorStateLocal);
            cfw.add(ByteCode.DUP);
            cfw.add(
                    ByteCode.GETFIELD,
                    OptRuntime.GeneratorState.CLASS_NAME,
                    OptRuntime.GeneratorState.thisObj_NAME,
                    OptRuntime.GeneratorState.thisObj_TYPE);
            cfw.addAStore(thisObjLocal);
            cfw.add(
                    ByteCode.GETFIELD,
                    OptRuntime.GeneratorState.CLASS_NAME,
                    OptRuntime.GeneratorState.activationFrame_NAME,
                    OptRuntime.GeneratorState.activationFrame_TYPE);
            cfw.addAStore(variableObjectLocal);

            if (epilogueLabel == -1) {
                epilogueLabel = cfw.acquireLabel();
            }

            List<Node> targets = ((FunctionNode) scriptOrFn).getResumptionPoints();
            if (targets != null) {
                // get resumption point
                generateGetGeneratorResumptionPoint();

                // generate dispatch table
                generatorSwitch = cfw.addTableSwitch(0, targets.size() + GENERATOR_START);
                generateCheckForThrowOrClose(-1, false, GENERATOR_START);
            }
        }

        if (compilerEnv.isGenerateObserverCount()) saveCurrentCodeOffset();

        // skip creating activation object or loading args for the body of a generator. The
        // activation record required by a generator has already been created
        // in generateGenerator().
        if (isGenerator) return;

        if (hasVarsInRegs) {
            // No need to create activation. Pad arguments if need be.
            int parmCount = scriptOrFn.getParamCount();
            if (parmCount > 0 && !inDirectCallFunction) {
                // Set up args array
                if (scriptOrFn.hasRestParameter()) {
                    cfw.addALoad(contextLocal);
                    cfw.addALoad(variableObjectLocal);
                    cfw.addALoad(argsLocal);
                    cfw.addPush(parmCount);
                    addScriptRuntimeInvoke(
                            "padAndRestArguments",
                            "("
                                    + "Lorg/mozilla/javascript/Context;"
                                    + "Lorg/mozilla/javascript/Scriptable;"
                                    + "[Ljava/lang/Object;"
                                    + "I"
                                    + ")[Ljava/lang/Object;");
                    cfw.addAStore(argsLocal);
                } else {
                    // check length of arguments, pad if need be
                    cfw.addALoad(argsLocal);
                    cfw.add(ByteCode.ARRAYLENGTH);
                    cfw.addPush(parmCount);
                    int label = cfw.acquireLabel();
                    cfw.add(ByteCode.IF_ICMPGE, label);
                    cfw.addALoad(argsLocal);
                    cfw.addPush(parmCount);
                    addScriptRuntimeInvoke(
                            "padArguments", "([Ljava/lang/Object;I)[Ljava/lang/Object;");
                    cfw.addAStore(argsLocal);
                    cfw.markLabel(label);
                }
            }

            int paramCount = fnCurrent.fnode.getParamCount();
            int varCount = fnCurrent.fnode.getParamAndVarCount();
            boolean[] constDeclarations = fnCurrent.fnode.getParamAndVarConst();

            // REMIND - only need to initialize the vars that don't get a value
            // before the next call and are used in the function
            int firstUndefVar = -1;
            for (int i = 0; i != varCount; ++i) {
                int reg = -1;
                if (i < paramCount) {
                    if (!inDirectCallFunction) {
                        reg = getNewWordLocal();
                        cfw.addALoad(argsLocal);
                        cfw.addPush(i);
                        cfw.add(ByteCode.AALOAD);
                        cfw.addAStore(reg);
                    }
                } else if (fnCurrent.isNumberVar(i)) {
                    reg = getNewWordPairLocal(constDeclarations[i]);
                    cfw.addPush(0.0);
                    cfw.addDStore(reg);
                } else {
                    reg = getNewWordLocal(constDeclarations[i]);
                    if (firstUndefVar == -1) {
                        Codegen.pushUndefined(cfw);
                        firstUndefVar = reg;
                    } else {
                        cfw.addALoad(firstUndefVar);
                    }
                    cfw.addAStore(reg);
                }
                if (reg >= 0) {
                    if (constDeclarations[i]) {
                        cfw.addPush(0);
                        cfw.addIStore(reg + (fnCurrent.isNumberVar(i) ? 2 : 1));
                    }
                    varRegisters[i] = reg;
                }

                // Add debug table entry if we're generating debug info
                if (compilerEnv.isGenerateDebugInfo()) {
                    String name = fnCurrent.fnode.getParamOrVarName(i);
                    String type = fnCurrent.isNumberVar(i) ? "D" : "Ljava/lang/Object;";
                    int startPC = cfw.getCurrentCodeOffset();
                    if (reg < 0) {
                        reg = varRegisters[i];
                    }
                    cfw.addVariableDescriptor(name, type, startPC, reg);
                }
            }

            // Skip creating activation object.
            return;
        }

        String debugVariableName;
        boolean isArrow = false;
        if (scriptOrFn instanceof FunctionNode) {
            isArrow = ((FunctionNode) scriptOrFn).getFunctionType() == FunctionNode.ARROW_FUNCTION;
        }
        if (fnCurrent != null) {
            debugVariableName = "activation";
            cfw.addALoad(funObjLocal);
            cfw.addALoad(contextLocal);
            cfw.addALoad(variableObjectLocal);
            cfw.addALoad(argsLocal);
            cfw.addPush(scriptOrFn.isInStrictMode());
            cfw.addPush(scriptOrFn.hasRestParameter());
            cfw.addPush(
                    !(scriptOrFn instanceof FunctionNode)
                            || ((FunctionNode) scriptOrFn).requiresArgumentObject());

            String methodName =
                    isArrow ? "createArrowFunctionActivation" : "createFunctionActivation";
            addScriptRuntimeInvoke(
                    methodName,
                    "(Lorg/mozilla/javascript/JSFunction;"
                            + "Lorg/mozilla/javascript/Context;"
                            + "Lorg/mozilla/javascript/Scriptable;"
                            + "[Ljava/lang/Object;"
                            + "Z"
                            + "Z"
                            + "Z"
                            + ")Lorg/mozilla/javascript/Scriptable;");
            cfw.addAStore(variableObjectLocal);
            cfw.addALoad(contextLocal);
            cfw.addALoad(variableObjectLocal);
            addScriptRuntimeInvoke(
                    "enterActivationFunction",
                    "(Lorg/mozilla/javascript/Context;"
                            + "Lorg/mozilla/javascript/Scriptable;"
                            + ")V");
        } else {
            debugVariableName = "global";
            cfw.addALoad(funObjLocal);
            cfw.addALoad(thisObjLocal);
            cfw.addALoad(contextLocal);
            cfw.addALoad(variableObjectLocal);
            cfw.addPush(0); // false to indicate it is not eval script
            addScriptRuntimeInvoke(
                    "initScript",
                    "(Lorg/mozilla/javascript/ScriptOrFn;"
                            + "Lorg/mozilla/javascript/Scriptable;"
                            + "Lorg/mozilla/javascript/Context;"
                            + "Lorg/mozilla/javascript/Scriptable;"
                            + "Z"
                            + ")V");
        }

        enterAreaStartLabel = cfw.acquireLabel();
        epilogueLabel = cfw.acquireLabel();
        cfw.markLabel(enterAreaStartLabel);

        generateNestedFunctionInits();

        // default is to generate debug info
        if (compilerEnv.isGenerateDebugInfo()) {
            cfw.addVariableDescriptor(
                    debugVariableName,
                    "Lorg/mozilla/javascript/Scriptable;",
                    cfw.getCurrentCodeOffset(),
                    variableObjectLocal);
        }

        if (fnCurrent == null) {
            // OPT: use dataflow to prove that this assignment is dead
            popvLocal = getNewWordLocal();
            Codegen.pushUndefined(cfw);
            cfw.addAStore(popvLocal);

            int linenum = scriptOrFn.getEndLineno();
            if (linenum != -1) cfw.addLineNumberEntry((short) linenum);

        } else {
            if (fnCurrent.itsContainsCalls0) {
                itsZeroArgArray = getNewWordLocal();
                cfw.add(
                        ByteCode.GETSTATIC,
                        "org/mozilla/javascript/ScriptRuntime",
                        "emptyArgs",
                        "[Ljava/lang/Object;");
                cfw.addAStore(itsZeroArgArray);
            }
            if (fnCurrent.itsContainsCalls1) {
                itsOneArgArray = getNewWordLocal();
                cfw.addPush(1);
                cfw.add(ByteCode.ANEWARRAY, "java/lang/Object");
                cfw.addAStore(itsOneArgArray);
            }
        }
    }

    private void generateGetGeneratorResumptionPoint() {
        cfw.addALoad(generatorStateLocal);
        cfw.add(
                ByteCode.GETFIELD,
                OptRuntime.GeneratorState.CLASS_NAME,
                OptRuntime.GeneratorState.resumptionPoint_NAME,
                OptRuntime.GeneratorState.resumptionPoint_TYPE);
    }

    private void generateSetGeneratorResumptionPoint(int nextState) {
        cfw.addALoad(generatorStateLocal);
        cfw.addLoadConstant(nextState);
        cfw.add(
                ByteCode.PUTFIELD,
                OptRuntime.GeneratorState.CLASS_NAME,
                OptRuntime.GeneratorState.resumptionPoint_NAME,
                OptRuntime.GeneratorState.resumptionPoint_TYPE);
    }

    private void generateGetGeneratorStackState() {
        cfw.addALoad(generatorStateLocal);
        addOptRuntimeInvoke("getGeneratorStackState", "(Ljava/lang/Object;)[Ljava/lang/Object;");
    }

    private void generateEpilogue() {
        if (compilerEnv.isGenerateObserverCount()) addInstructionCount();
        if (isGenerator) {
            // generate locals initialization
            Map<Node, int[]> liveLocals = ((FunctionNode) scriptOrFn).getLiveLocals();
            if (liveLocals != null) {
                List<Node> nodes = ((FunctionNode) scriptOrFn).getResumptionPoints();
                for (Node node : nodes) {
                    int[] live = liveLocals.get(node);
                    if (live != null) {
                        cfw.markTableSwitchCase(generatorSwitch, getNextGeneratorState(node));
                        generateGetGeneratorLocalsState();
                        for (int j = 0; j < live.length; j++) {
                            cfw.add(ByteCode.DUP);
                            cfw.addLoadConstant(j);
                            cfw.add(ByteCode.AALOAD);
                            cfw.addAStore(live[j]);
                        }
                        cfw.add(ByteCode.POP);
                        cfw.add(ByteCode.GOTO, getTargetLabel(node));
                    }
                }
            }

            // generate dispatch tables for finally
            if (finallys != null) {
                for (Map.Entry<Node, FinallyReturnPoint> e : finallys.entrySet()) {
                    if (e.getKey().getType() == Token.FINALLY) {
                        FinallyReturnPoint ret = e.getValue();
                        // the finally will jump here
                        cfw.markLabel(ret.tableLabel, 1);

                        // start generating a dispatch table
                        int startSwitch = cfw.addTableSwitch(0, ret.jsrPoints.size() - 1);
                        int c = 0;
                        cfw.markTableSwitchDefault(startSwitch);
                        for (int i = 0; i < ret.jsrPoints.size(); i++) {
                            // generate gotos back to the JSR location
                            cfw.markTableSwitchCase(startSwitch, c);
                            cfw.add(ByteCode.GOTO, ret.jsrPoints.get(i).intValue());
                            c++;
                        }
                    }
                }
            }
        }

        if (epilogueLabel != -1) {
            cfw.markLabel(epilogueLabel);
        }

        if (isGenerator) {
            if (((FunctionNode) scriptOrFn).getResumptionPoints() != null) {
                cfw.markTableSwitchDefault(generatorSwitch);
            }

            // change state for re-entry
            generateSetGeneratorResumptionPoint(GENERATOR_TERMINATE);

            // throw StopIteration. Needs scope as well as the generator state
            cfw.addALoad(variableObjectLocal);
            cfw.addALoad(generatorStateLocal);
            addOptRuntimeInvoke("throwStopIteration", "(Ljava/lang/Object;Ljava/lang/Object;)V");

            Codegen.pushUndefined(cfw);
            cfw.add(ByteCode.ARETURN);

        } else if (hasVarsInRegs) {
            cfw.add(ByteCode.ARETURN);

        } else if (fnCurrent == null) {
            cfw.addALoad(popvLocal);
            cfw.add(ByteCode.ARETURN);

        } else {
            generateActivationExit();
            cfw.add(ByteCode.ARETURN);

            // Generate catch block to catch all and rethrow to call exit code
            // under exception propagation as well.

            int finallyHandler = cfw.acquireLabel();
            cfw.markHandler(finallyHandler);
            short exceptionObject = getNewWordLocal();
            cfw.addAStore(exceptionObject);

            // Duplicate generateActivationExit() in the catch block since it
            // takes less space then full-featured ByteCode.JSR/ByteCode.RET
            generateActivationExit();

            cfw.addALoad(exceptionObject);
            releaseWordLocal(exceptionObject);
            // rethrow
            cfw.add(ByteCode.ATHROW);

            // mark the handler
            cfw.addExceptionHandler(
                    enterAreaStartLabel, epilogueLabel, finallyHandler, null); // catch any
        }
    }

    private void generateGetGeneratorLocalsState() {
        cfw.addALoad(generatorStateLocal);
        addOptRuntimeInvoke("getGeneratorLocalsState", "(Ljava/lang/Object;)[Ljava/lang/Object;");
    }

    private void generateSetGeneratorReturnValue() {
        cfw.addALoad(generatorStateLocal);
        cfw.add(ByteCode.SWAP);
        addOptRuntimeInvoke("setGeneratorReturnValue", "(Ljava/lang/Object;Ljava/lang/Object;)V");
    }

    private void generateActivationExit() {
        if (fnCurrent == null || hasVarsInRegs) throw Kit.codeBug();
        cfw.addALoad(contextLocal);
        addScriptRuntimeInvoke("exitActivationFunction", "(Lorg/mozilla/javascript/Context;)V");
    }

    private void generateStatement(Node node) {
        updateLineNumber(node);
        int type = node.getType();
        Node child = node.getFirstChild();
        switch (type) {
            case Token.LOOP:
            case Token.LABEL:
            case Token.WITH:
            case Token.SCRIPT:
            case Token.BLOCK:
            case Token.EMPTY:
                // no-ops.
                if (compilerEnv.isGenerateObserverCount()) {
                    // Need to add instruction count even for no-ops to catch
                    // cases like while (1) {}
                    addInstructionCount(1);
                }
                while (child != null) {
                    generateStatement(child);
                    child = child.getNext();
                }
                break;

            case Token.LOCAL_BLOCK:
                {
                    boolean prevLocal = inLocalBlock;
                    inLocalBlock = true;
                    int local = getNewWordLocal();
                    if (isGenerator) {
                        cfw.add(ByteCode.ACONST_NULL);
                        cfw.addAStore(local);
                    }
                    node.putIntProp(Node.LOCAL_PROP, local);
                    while (child != null) {
                        generateStatement(child);
                        child = child.getNext();
                    }
                    releaseWordLocal((short) local);
                    node.removeProp(Node.LOCAL_PROP);
                    inLocalBlock = prevLocal;
                    break;
                }

            case Token.FUNCTION:
                {
                    int fnIndex = node.getExistingIntProp(Node.FUNCTION_PROP);
                    OptFunctionNode ofn = OptFunctionNode.get(scriptOrFn, fnIndex);
                    int t = ofn.fnode.getFunctionType();
                    if (t == FunctionNode.FUNCTION_EXPRESSION_STATEMENT) {
                        visitFunction(ofn, t);
                    } else {
                        if (t != FunctionNode.FUNCTION_STATEMENT) {
                            throw Codegen.badTree();
                        }
                    }
                    break;
                }

            case Token.TRY:
                visitTryCatchFinally((Jump) node, child);
                break;

            case Token.CATCH_SCOPE:
                {
                    // nothing stays on the stack on entry into a catch scope
                    cfw.setStackTop((short) 0);

                    int local = getLocalBlockRegister(node);
                    int scopeIndex = node.getExistingIntProp(Node.CATCH_SCOPE_PROP);

                    String name = null;
                    if (child.getType() == Token.NAME) {
                        name = child.getString(); // name of exception
                    }
                    child = child.getNext();
                    generateExpression(child, node); // load expression object
                    if (scopeIndex == 0) {
                        cfw.add(ByteCode.ACONST_NULL);
                    } else {
                        // Load previous catch scope object
                        cfw.addALoad(local);
                    }
                    if (name != null) {
                        cfw.addPush(name);
                    } else {
                        cfw.add(ByteCode.ACONST_NULL);
                    }
                    cfw.addALoad(contextLocal);
                    cfw.addALoad(variableObjectLocal);

                    addScriptRuntimeInvoke(
                            "newCatchScope",
                            "(Ljava/lang/Throwable;"
                                    + "Lorg/mozilla/javascript/Scriptable;"
                                    + "Ljava/lang/String;"
                                    + "Lorg/mozilla/javascript/Context;"
                                    + "Lorg/mozilla/javascript/Scriptable;"
                                    + ")Lorg/mozilla/javascript/Scriptable;");
                    cfw.addAStore(local);
                }
                break;

            case Token.THROW:
                generateExpression(child, node);
                if (compilerEnv.isGenerateObserverCount()) addInstructionCount();
                generateThrowJavaScriptException();
                break;

            case Token.RETHROW:
                if (compilerEnv.isGenerateObserverCount()) addInstructionCount();
                cfw.addALoad(getLocalBlockRegister(node));
                cfw.add(ByteCode.ATHROW);
                break;

            case Token.RETURN_RESULT:
            case Token.RETURN:
                if (child != null) {
                    generateExpression(child, node);
                } else if (type == Token.RETURN) {
                    Codegen.pushUndefined(cfw);
                } else {
                    if (popvLocal < 0) throw Codegen.badTree();
                    cfw.addALoad(popvLocal);
                }
                if (isGenerator) {
                    // Stash away the return value for use in the epilogue.
                    generateSetGeneratorReturnValue();
                }

                if (compilerEnv.isGenerateObserverCount()) addInstructionCount();
                if (epilogueLabel == -1) {
                    if (!hasVarsInRegs) throw Codegen.badTree();
                    epilogueLabel = cfw.acquireLabel();
                }
                cfw.add(ByteCode.GOTO, epilogueLabel);
                break;

            case Token.SWITCH:
                if (compilerEnv.isGenerateObserverCount()) addInstructionCount();
                visitSwitch((Jump) node, child);
                break;

            case Token.ENTERWITH:
                generateExpression(child, node);
                cfw.addALoad(contextLocal);
                cfw.addALoad(variableObjectLocal);
                addScriptRuntimeInvoke(
                        "enterWith",
                        "(Ljava/lang/Object;"
                                + "Lorg/mozilla/javascript/Context;"
                                + "Lorg/mozilla/javascript/Scriptable;"
                                + ")Lorg/mozilla/javascript/Scriptable;");
                cfw.addAStore(variableObjectLocal);
                incReferenceWordLocal(variableObjectLocal);
                break;

            case Token.LEAVEWITH:
                cfw.addALoad(variableObjectLocal);
                addScriptRuntimeInvoke(
                        "leaveWith",
                        "(Lorg/mozilla/javascript/Scriptable;"
                                + ")Lorg/mozilla/javascript/Scriptable;");
                cfw.addAStore(variableObjectLocal);
                decReferenceWordLocal(variableObjectLocal);
                break;

            case Token.ENUM_INIT_KEYS:
            case Token.ENUM_INIT_VALUES:
            case Token.ENUM_INIT_ARRAY:
            case Token.ENUM_INIT_VALUES_IN_ORDER:
                generateExpression(child, node);
                cfw.addALoad(contextLocal);
                cfw.addALoad(variableObjectLocal);
                int enumType =
                        type == Token.ENUM_INIT_KEYS
                                ? ScriptRuntime.ENUMERATE_KEYS
                                : type == Token.ENUM_INIT_VALUES
                                        ? ScriptRuntime.ENUMERATE_VALUES
                                        : type == Token.ENUM_INIT_VALUES_IN_ORDER
                                                ? ScriptRuntime.ENUMERATE_VALUES_IN_ORDER
                                                : ScriptRuntime.ENUMERATE_ARRAY;
                cfw.addPush(enumType);
                addScriptRuntimeInvoke(
                        "enumInit",
                        "(Ljava/lang/Object;"
                                + "Lorg/mozilla/javascript/Context;"
                                + "Lorg/mozilla/javascript/Scriptable;"
                                + "I"
                                + ")Ljava/lang/Object;");
                cfw.addAStore(getLocalBlockRegister(node));
                break;

            case Token.EXPR_VOID:
                if (child.getType() == Token.SETVAR) {
                    /* special case this so as to avoid unnecessary
                    load's & pop's */
                    visitSetVar(child, child.getFirstChild(), false);
                } else if (child.getType() == Token.SETCONSTVAR) {
                    /* special case this so as to avoid unnecessary
                    load's & pop's */
                    visitSetConstVar(child, child.getFirstChild(), false);
                } else if ((child.getType() == Token.YIELD)
                        || (child.getType() == Token.YIELD_STAR)) {
                    generateYieldPoint(child, false);
                } else {
                    generateExpression(child, node);
                    if (node.getIntProp(Node.ISNUMBER_PROP, -1) != -1) cfw.add(ByteCode.POP2);
                    else cfw.add(ByteCode.POP);
                }
                break;

            case Token.EXPR_RESULT:
                generateExpression(child, node);
                if (popvLocal < 0) {
                    popvLocal = getNewWordLocal();
                }
                cfw.addAStore(popvLocal);
                break;

            case Token.TARGET:
                {
                    if (compilerEnv.isGenerateObserverCount()) addInstructionCount();
                    int label = getTargetLabel(node);
                    cfw.markLabel(label);
                    if (compilerEnv.isGenerateObserverCount()) saveCurrentCodeOffset();
                }
                break;

            case Token.JSR:
            case Token.GOTO:
            case Token.IFEQ:
            case Token.IFNE:
                if (compilerEnv.isGenerateObserverCount()) addInstructionCount();
                visitGoto((Jump) node, type, child);
                break;

            case Token.FINALLY:
                {
                    // This is the non-exception case for a finally block. In
                    // other words, since we inline finally blocks wherever
                    // jsr was previously used, and jsr is only used when the
                    // function is not a generator, we don't need to generate
                    // this case if the function isn't a generator.
                    if (!isGenerator) {
                        break;
                    }

                    if (compilerEnv.isGenerateObserverCount()) saveCurrentCodeOffset();
                    // there is exactly one value on the stack when enterring
                    // finally blocks: the return address (or its int encoding)
                    cfw.setStackTop((short) 1);

                    // Save return address in a new local
                    int finallyRegister = getNewWordLocal();

                    int finallyStart = cfw.acquireLabel();
                    int finallyEnd = cfw.acquireLabel();
                    cfw.markLabel(finallyStart);

                    generateIntegerWrap();
                    cfw.addAStore(finallyRegister);

                    while (child != null) {
                        generateStatement(child);
                        child = child.getNext();
                    }

                    cfw.addALoad(finallyRegister);
                    cfw.add(ByteCode.CHECKCAST, "java/lang/Integer");
                    generateIntegerUnwrap();
                    FinallyReturnPoint ret = finallys.get(node);
                    ret.tableLabel = cfw.acquireLabel();
                    cfw.add(ByteCode.GOTO, ret.tableLabel);

                    // After this GOTO we expect stack to be empty again!
                    cfw.setStackTop((short) 0);

                    releaseWordLocal((short) finallyRegister);
                    cfw.markLabel(finallyEnd);
                }
                break;

            case Token.DEBUGGER:
                break;

            default:
                throw Codegen.badTree();
        }
    }

    private void generateIntegerWrap() {
        cfw.addInvoke(
                ByteCode.INVOKESTATIC, "java/lang/Integer", "valueOf", "(I)Ljava/lang/Integer;");
    }

    private void generateIntegerUnwrap() {
        cfw.addInvoke(ByteCode.INVOKEVIRTUAL, "java/lang/Integer", "intValue", "()I");
    }

    private void generateThrowJavaScriptException() {
        cfw.add(ByteCode.NEW, "org/mozilla/javascript/JavaScriptException");
        cfw.add(ByteCode.DUP_X1);
        cfw.add(ByteCode.SWAP);
        cfw.addPush(scriptOrFn.getSourceName());
        cfw.addPush(itsLineNumber);
        cfw.addInvoke(
                ByteCode.INVOKESPECIAL,
                "org/mozilla/javascript/JavaScriptException",
                "<init>",
                "(Ljava/lang/Object;Ljava/lang/String;I)V");
        cfw.add(ByteCode.ATHROW);
    }

    private int getNextGeneratorState(Node node) {
        int nodeIndex = ((FunctionNode) scriptOrFn).getResumptionPoints().indexOf(node);
        return nodeIndex + GENERATOR_YIELD_START;
    }

    private void generateExpression(Node node, Node parent) {
        int type = node.getType();
        Node child = node.getFirstChild();
        switch (type) {
            case Token.USE_STACK:
                break;

            case Token.FUNCTION:
                if (fnCurrent != null || parent.getType() != Token.SCRIPT) {
                    int fnIndex = node.getExistingIntProp(Node.FUNCTION_PROP);
                    OptFunctionNode ofn = OptFunctionNode.get(scriptOrFn, fnIndex);
                    int t = ofn.fnode.getFunctionType();
                    if (t != FunctionNode.FUNCTION_EXPRESSION && t != FunctionNode.ARROW_FUNCTION) {
                        throw Codegen.badTree();
                    }
                    visitFunction(ofn, t);
                }
                break;

            case Token.NAME:
                {
                    cfw.addALoad(variableObjectLocal);
                    cfw.addALoad(contextLocal);
                    addDynamicInvoke("NAME:GET:" + node.getString(), Signatures.NAME_GET);
                }
                break;

            case Token.CALL:
            case Token.NEW:
                {
                    int specialType = node.getIntProp(Node.SPECIALCALL_PROP, Node.NON_SPECIALCALL);
                    if (specialType == Node.NON_SPECIALCALL) {
                        OptFunctionNode target;
                        target = (OptFunctionNode) node.getProp(Node.DIRECTCALL_PROP);

                        if (target != null) {
                            visitOptimizedCall(node, target, type, child);
                        } else if (type == Token.CALL) {
                            visitStandardCall(node, child);
                        } else {
                            visitStandardNew(node, child);
                        }
                    } else {
                        visitSpecialCall(node, type, specialType, child);
                    }
                }
                break;

            case Token.REF_CALL:
                generateFunctionAndThisObj(child, node);
                // stack: ... functionObj thisObj
                child = child.getNext();
                generateCallArgArray(node, child, false);
                cfw.addALoad(contextLocal);
                addScriptRuntimeInvoke(
                        "callRef",
                        "(Lorg/mozilla/javascript/Callable;"
                                + "Lorg/mozilla/javascript/Scriptable;"
                                + "[Ljava/lang/Object;"
                                + "Lorg/mozilla/javascript/Context;"
                                + ")Lorg/mozilla/javascript/Ref;");
                break;

            case Token.NUMBER:
                {
                    double num = node.getDouble();
                    if (node.getIntProp(Node.ISNUMBER_PROP, -1) != -1) {
                        cfw.addPush(num);
                    } else {
                        codegen.pushNumberAsObject(cfw, num);
                    }
                }
                break;

            case Token.BIGINT:
                {
                    byte[] bytes = node.getBigInt().toByteArray();
                    cfw.add(ByteCode.NEW, "java/math/BigInteger");
                    cfw.add(ByteCode.DUP);
                    cfw.addPush(bytes.length);
                    cfw.add(ByteCode.NEWARRAY, ByteCode.T_BYTE);
                    for (int i = 0; i < bytes.length; i++) {
                        cfw.add(ByteCode.DUP);
                        cfw.addPush(i);
                        cfw.add(ByteCode.BIPUSH, bytes[i]);
                        cfw.add(ByteCode.BASTORE);
                    }
                    cfw.addInvoke(
                            ByteCode.INVOKESPECIAL, "java/math/BigInteger", "<init>", "([B)V");
                }
                break;

            case Token.STRING:
                cfw.addPush(node.getString());
                break;

            case Token.THIS:
                cfw.addALoad(thisObjLocal);
                break;

            case Token.SUPER:
                {
                    // See 9.1.1.3.5 GetSuperBase

                    // Read home object from activation, which we know we'll always have because we
                    // set "requiresActivation" to any function that mentions "super" in IRFactory
                    cfw.addALoad(variableObjectLocal);
                    cfw.add(ByteCode.CHECKCAST, "org/mozilla/javascript/NativeCall");
                    cfw.addInvoke(
                            ByteCode.INVOKEVIRTUAL,
                            "org/mozilla/javascript/NativeCall",
                            "getHomeObject",
                            "()Lorg/mozilla/javascript/Scriptable;");

                    // If null, then put undefined
                    int after = cfw.acquireLabel();
                    int putPrototype = cfw.acquireLabel();
                    cfw.add(ByteCode.DUP);
                    cfw.add(ByteCode.IFNONNULL, putPrototype);

                    cfw.add(ByteCode.POP);
                    cfw.add(
                            ByteCode.GETSTATIC,
                            "org/mozilla/javascript/Undefined",
                            "instance",
                            "Ljava/lang/Object;");
                    cfw.add(ByteCode.GOTO, after);

                    // Otherwise put the prototype
                    cfw.markLabel(putPrototype);
                    cfw.addInvoke(
                            ByteCode.INVOKEINTERFACE,
                            "org/mozilla/javascript/Scriptable",
                            "getPrototype",
                            "()Lorg/mozilla/javascript/Scriptable;");

                    cfw.markLabel(after);
                    break;
                }

            case Token.THISFN:
                cfw.addALoad(funObjLocal);
                break;

            case Token.NULL:
                cfw.add(ByteCode.ACONST_NULL);
                break;

            case Token.UNDEFINED:
                Codegen.pushUndefined(cfw);
                break;

            case Token.TRUE:
                cfw.add(ByteCode.GETSTATIC, "java/lang/Boolean", "TRUE", "Ljava/lang/Boolean;");
                break;

            case Token.FALSE:
                cfw.add(ByteCode.GETSTATIC, "java/lang/Boolean", "FALSE", "Ljava/lang/Boolean;");
                break;

            case Token.REGEXP:
                {
                    // Create a new wrapper around precompiled regexp
                    cfw.addALoad(contextLocal);
                    cfw.addALoad(variableObjectLocal);
                    int i = node.getExistingIntProp(Node.REGEXP_PROP);
                    cfw.add(
                            ByteCode.GETSTATIC,
                            codegen.mainClassName,
                            codegen.getCompiledRegexpName(scriptOrFn, i),
                            "Ljava/lang/Object;");
                    cfw.addInvoke(
                            ByteCode.INVOKESTATIC,
                            "org/mozilla/javascript/ScriptRuntime",
                            "wrapRegExp",
                            "(Lorg/mozilla/javascript/Context;"
                                    + "Lorg/mozilla/javascript/Scriptable;"
                                    + "Ljava/lang/Object;"
                                    + ")Lorg/mozilla/javascript/Scriptable;");
                }
                break;

            case Token.COMMA:
                {
                    Node next = child.getNext();
                    while (next != null) {
                        generateExpression(child, node);
                        cfw.add(ByteCode.POP);
                        child = next;
                        next = next.getNext();
                    }
                    generateExpression(child, node);
                    break;
                }

            case Token.ENUM_NEXT:
            case Token.ENUM_ID:
                {
                    int local = getLocalBlockRegister(node);
                    cfw.addALoad(local);
                    cfw.addALoad(contextLocal);
                    if (type == Token.ENUM_NEXT) {
                        addScriptRuntimeInvoke(
                                "enumNext",
                                "(Ljava/lang/Object;"
                                        + "Lorg/mozilla/javascript/Context;"
                                        + ")Ljava/lang/Boolean;");
                    } else {
                        addScriptRuntimeInvoke(
                                "enumId",
                                "(Ljava/lang/Object;"
                                        + "Lorg/mozilla/javascript/Context;"
                                        + ")Ljava/lang/Object;");
                    }
                    break;
                }

            case Token.ARRAYLIT:
                visitArrayLiteral(node, child, false);
                break;

            case Token.OBJECTLIT:
                visitObjectLiteral(node, child, false);
                break;

            case Token.NOT:
                {
                    int trueTarget = cfw.acquireLabel();
                    int falseTarget = cfw.acquireLabel();
                    int beyond = cfw.acquireLabel();
                    generateIfJump(child, node, trueTarget, falseTarget);

                    cfw.markLabel(trueTarget);
                    cfw.add(
                            ByteCode.GETSTATIC,
                            "java/lang/Boolean",
                            "FALSE",
                            "Ljava/lang/Boolean;");
                    cfw.add(ByteCode.GOTO, beyond);
                    cfw.markLabel(falseTarget);
                    cfw.add(ByteCode.GETSTATIC, "java/lang/Boolean", "TRUE", "Ljava/lang/Boolean;");
                    cfw.markLabel(beyond);
                    cfw.adjustStackTop(-1);
                    break;
                }

            case Token.BITNOT:
                visitBitNot(node, child);
                break;

            case Token.VOID:
                generateExpression(child, node);
                cfw.add(ByteCode.POP);
                Codegen.pushUndefined(cfw);
                break;

            case Token.TYPEOF:
                generateExpression(child, node);
                addScriptRuntimeInvoke("typeof", "(Ljava/lang/Object;" + ")Ljava/lang/String;");
                break;

            case Token.TYPEOFNAME:
                visitTypeofname(node);
                break;

            case Token.INC:
            case Token.DEC:
                visitIncDec(node);
                break;

            case Token.OR:
            case Token.AND:
                {
                    generateExpression(child, node);
                    cfw.add(ByteCode.DUP);
                    addDynamicInvoke("MATH:TOBOOLEAN", Signatures.MATH_TO_BOOLEAN);
                    int falseTarget = cfw.acquireLabel();
                    if (type == Token.AND) cfw.add(ByteCode.IFEQ, falseTarget);
                    else cfw.add(ByteCode.IFNE, falseTarget);
                    cfw.add(ByteCode.POP);
                    generateExpression(child.getNext(), node);
                    cfw.markLabel(falseTarget);
                }
                break;

            case Token.HOOK:
                {
                    Node ifThen = child.getNext();
                    Node ifElse = ifThen.getNext();
                    generateExpression(child, node);
                    addDynamicInvoke("MATH:TOBOOLEAN", Signatures.MATH_TO_BOOLEAN);
                    int elseTarget = cfw.acquireLabel();
                    cfw.add(ByteCode.IFEQ, elseTarget);
                    int stack = cfw.getStackTop();
                    generateExpression(ifThen, node);
                    int afterHook = cfw.acquireLabel();
                    cfw.add(ByteCode.GOTO, afterHook);
                    cfw.markLabel(elseTarget, stack);
                    generateExpression(ifElse, node);
                    cfw.markLabel(afterHook);
                }
                break;

            case Token.ADD:
                {
                    generateExpression(child, node);
                    generateExpression(child.getNext(), node);
                    switch (node.getIntProp(Node.ISNUMBER_PROP, -1)) {
                        case Node.BOTH:
                            cfw.add(ByteCode.DADD);
                            break;
                        case Node.LEFT:
                            cfw.addALoad(contextLocal);
                            addOptRuntimeInvoke(
                                    "add",
                                    "(DLjava/lang/Object;Lorg/mozilla/javascript/Context;)Ljava/lang/Object;");
                            break;
                        case Node.RIGHT:
                            cfw.addALoad(contextLocal);
                            addOptRuntimeInvoke(
                                    "add",
                                    "(Ljava/lang/Object;DLorg/mozilla/javascript/Context;)Ljava/lang/Object;");
                            break;
                        default:
                            cfw.addALoad(contextLocal);
                            addDynamicInvoke("MATH:ADD", Signatures.MATH_ADD);
                    }
                }
                break;

            case Token.STRING_CONCAT:
                generateExpression(child, node);
                generateExpression(child.getNext(), node);
                addScriptRuntimeInvoke(
                        "concat", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");
                break;

            case Token.SUB:
            case Token.MUL:
            case Token.DIV:
            case Token.MOD:
                visitArithmetic(node, type, child, parent);
                break;

            case Token.EXP:
                visitExponentiation(node, child, parent);
                break;

            case Token.BITOR:
            case Token.BITXOR:
            case Token.BITAND:
            case Token.LSH:
            case Token.RSH:
            case Token.URSH:
                visitBitOp(node, type, child);
                break;

            case Token.POS:
                {
                    int childNumberFlag = node.getIntProp(Node.ISNUMBER_PROP, -1);
                    generateExpression(child, node);
                    if (childNumberFlag == -1) {
                        addObjectToDouble();
                        addDoubleWrap();
                    }
                    break;
                }

            case Token.NEG:
                {
                    int childNumberFlag = node.getIntProp(Node.ISNUMBER_PROP, -1);
                    generateExpression(child, node);
                    if (childNumberFlag == -1) {
                        addObjectToNumeric();
                        addScriptRuntimeInvoke(
                                "negate", "(Ljava/lang/Number;" + ")Ljava/lang/Number;");
                    } else {
                        cfw.add(ByteCode.DNEG);
                    }
                    break;
                }

            case Token.TO_DOUBLE:
                // cnvt to double (not Double)
                generateExpression(child, node);
                addObjectToDouble();
                break;

            case Token.TO_OBJECT:
                {
                    // convert from double
                    int prop = -1;
                    if (child.getType() == Token.NUMBER) {
                        prop = child.getIntProp(Node.ISNUMBER_PROP, -1);
                    }
                    if (prop != -1) {
                        child.removeProp(Node.ISNUMBER_PROP);
                        generateExpression(child, node);
                        child.putIntProp(Node.ISNUMBER_PROP, prop);
                    } else {
                        generateExpression(child, node);
                        addDoubleWrap();
                    }
                    break;
                }

            case Token.IN:
            case Token.INSTANCEOF:
            case Token.LE:
            case Token.LT:
            case Token.GE:
            case Token.GT:
                {
                    int trueGOTO = cfw.acquireLabel();
                    int falseGOTO = cfw.acquireLabel();
                    visitIfJumpRelOp(node, child, trueGOTO, falseGOTO);
                    addJumpedBooleanWrap(trueGOTO, falseGOTO);
                    break;
                }

            case Token.EQ:
            case Token.NE:
            case Token.SHEQ:
            case Token.SHNE:
                {
                    int trueGOTO = cfw.acquireLabel();
                    int falseGOTO = cfw.acquireLabel();
                    visitIfJumpEqOp(node, child, trueGOTO, falseGOTO);
                    addJumpedBooleanWrap(trueGOTO, falseGOTO);
                    break;
                }

            case Token.GETPROP:
            case Token.GETPROPNOWARN:
                visitGetProp(node, child);
                break;

            case Token.GETELEM:
                generateExpression(child, node); // object
                if (node.getIntProp(Node.OPTIONAL_CHAINING, 0) == 1) {
                    int getElem = cfw.acquireLabel();
                    int after = cfw.acquireLabel();

                    cfw.add(ByteCode.DUP);
                    addOptRuntimeInvoke("isNullOrUndefined", "(Ljava/lang/Object;)Z");
                    cfw.add(ByteCode.IFEQ, getElem);

                    cfw.add(ByteCode.POP);
                    Codegen.pushUndefined(cfw);
                    cfw.add(ByteCode.GOTO, after);

                    cfw.markLabel(getElem);
                    finishGetElemGeneration(node, child);
                    cfw.markLabel(after);
                } else {
                    finishGetElemGeneration(node, child);
                }
                break;

            case Token.GET_REF:
                generateExpression(child, node); // reference
                if (node.getIntProp(Node.OPTIONAL_CHAINING, 0) == 1) {
                    int getRef = cfw.acquireLabel();
                    int after = cfw.acquireLabel();

                    cfw.add(ByteCode.DUP);

                    addOptRuntimeInvoke("isNullOrUndefined", "(Ljava/lang/Object;)Z");
                    cfw.add(ByteCode.IFEQ, getRef);
                    cfw.add(ByteCode.GOTO, after);

                    cfw.markLabel(getRef);
                    cfw.add(ByteCode.CHECKCAST, "org/mozilla/javascript/Ref");
                    finishGetRefGeneration();
                    cfw.markLabel(after);
                } else {
                    finishGetRefGeneration();
                }
                break;

            case Token.GETVAR:
                visitGetVar(node);
                break;

            case Token.SETVAR:
                visitSetVar(node, child, true);
                break;

            case Token.SETNAME:
                visitSetName(node, child);
                break;

            case Token.STRICT_SETNAME:
                visitStrictSetName(node, child);
                break;

            case Token.SETCONST:
                visitSetConst(node, child);
                break;

            case Token.SETCONSTVAR:
                visitSetConstVar(node, child, true);
                break;

            case Token.SETPROP:
            case Token.SETPROP_OP:
                visitSetProp(type, node, child);
                break;

            case Token.SETELEM:
            case Token.SETELEM_OP:
                visitSetElem(type, node, child);
                break;

            case Token.SET_REF:
            case Token.SET_REF_OP:
                {
                    generateExpression(child, node);
                    child = child.getNext();
                    if (type == Token.SET_REF_OP) {
                        cfw.add(ByteCode.DUP);
                        cfw.addALoad(contextLocal);
                        addScriptRuntimeInvoke(
                                "refGet",
                                "(Lorg/mozilla/javascript/Ref;"
                                        + "Lorg/mozilla/javascript/Context;"
                                        + ")Ljava/lang/Object;");
                    }
                    generateExpression(child, node);
                    cfw.addALoad(contextLocal);
                    cfw.addALoad(variableObjectLocal);
                    addScriptRuntimeInvoke(
                            "refSet",
                            "(Lorg/mozilla/javascript/Ref;"
                                    + "Ljava/lang/Object;"
                                    + "Lorg/mozilla/javascript/Context;"
                                    + "Lorg/mozilla/javascript/Scriptable;"
                                    + ")Ljava/lang/Object;");
                }
                break;

            case Token.DEL_REF:
                generateExpression(child, node);
                cfw.addALoad(contextLocal);
                addScriptRuntimeInvoke(
                        "refDel",
                        "(Lorg/mozilla/javascript/Ref;"
                                + "Lorg/mozilla/javascript/Context;"
                                + ")Ljava/lang/Object;");
                break;

            case Token.DELPROP:
                boolean isName = child.getType() == Token.BINDNAME;
                generateExpression(child, node);
                child = child.getNext();
                generateExpression(child, node);
                if (node.getIntProp(Node.SUPER_PROPERTY_ACCESS, 0) == 1) {
                    // We have pushed `super` and the expression, but we need to remove them because
                    // we actually are just going to throw an error. However, delete is supposed to
                    // put a boolean on the stack and the class file writer would complain if we
                    // don't have only popped here. So we pop and the push 0 (false). Anyway, this
                    // is code that will always fail, so honestly no one will ever write something
                    // like this (delete super[foo]), so... even if this is not the most efficient
                    // bytecode, it's fine.
                    cfw.add(ByteCode.POP2);
                    cfw.addLoadConstant(0);
                    addScriptRuntimeInvoke("throwDeleteOnSuperPropertyNotAllowed", "()V");
                } else {
                    cfw.addALoad(contextLocal);
                    cfw.addPush(isName);
                    addScriptRuntimeInvoke(
                            "delete",
                            "(Ljava/lang/Object;"
                                    + "Ljava/lang/Object;"
                                    + "Lorg/mozilla/javascript/Context;"
                                    + "Z)Ljava/lang/Object;");
                }
                break;

            case Token.BINDNAME:
                {
                    while (child != null) {
                        generateExpression(child, node);
                        child = child.getNext();
                    }
                    // Generate code for "ScriptRuntime.bind(varObj, "s")"
                    cfw.addALoad(variableObjectLocal);
                    cfw.addALoad(contextLocal);
                    addDynamicInvoke("NAME:BIND:" + node.getString(), Signatures.NAME_BIND);
                }
                break;

            case Token.LOCAL_LOAD:
                cfw.addALoad(getLocalBlockRegister(node));
                break;

            case Token.REF_SPECIAL:
                {
                    generateExpression(child, node);
                    if (node.getIntProp(Node.OPTIONAL_CHAINING, 0) == 1) {
                        int getExpr = cfw.acquireLabel();
                        int after = cfw.acquireLabel();

                        cfw.add(ByteCode.DUP);

                        addOptRuntimeInvoke("isNullOrUndefined", "(Ljava/lang/Object;)Z");
                        cfw.add(ByteCode.IFEQ, getExpr);

                        cfw.add(ByteCode.POP);
                        Codegen.pushUndefined(cfw);
                        cfw.add(ByteCode.GOTO, after);

                        cfw.markLabel(getExpr);
                        finishRefSpecialGeneration(node);
                        cfw.markLabel(after);
                    } else {
                        finishRefSpecialGeneration(node);
                    }
                }
                break;
            case Token.REF_MEMBER:
            case Token.REF_NS_MEMBER:
            case Token.REF_NAME:
            case Token.REF_NS_NAME:
                {
                    int memberTypeFlags = node.getIntProp(Node.MEMBER_TYPE_PROP, 0);
                    // generate possible target, possible namespace and member
                    do {
                        generateExpression(child, node);
                        child = child.getNext();
                    } while (child != null);
                    cfw.addALoad(contextLocal);
                    String methodName, signature;
                    switch (type) {
                        case Token.REF_MEMBER:
                            methodName = "memberRef";
                            signature =
                                    "(Ljava/lang/Object;"
                                            + "Ljava/lang/Object;"
                                            + "Lorg/mozilla/javascript/Context;"
                                            + "I"
                                            + ")Lorg/mozilla/javascript/Ref;";
                            break;
                        case Token.REF_NS_MEMBER:
                            methodName = "memberRef";
                            signature =
                                    "(Ljava/lang/Object;"
                                            + "Ljava/lang/Object;"
                                            + "Ljava/lang/Object;"
                                            + "Lorg/mozilla/javascript/Context;"
                                            + "I"
                                            + ")Lorg/mozilla/javascript/Ref;";
                            break;
                        case Token.REF_NAME:
                            methodName = "nameRef";
                            signature =
                                    "(Ljava/lang/Object;"
                                            + "Lorg/mozilla/javascript/Context;"
                                            + "Lorg/mozilla/javascript/Scriptable;"
                                            + "I"
                                            + ")Lorg/mozilla/javascript/Ref;";
                            cfw.addALoad(variableObjectLocal);
                            break;
                        case Token.REF_NS_NAME:
                            methodName = "nameRef";
                            signature =
                                    "(Ljava/lang/Object;"
                                            + "Ljava/lang/Object;"
                                            + "Lorg/mozilla/javascript/Context;"
                                            + "Lorg/mozilla/javascript/Scriptable;"
                                            + "I"
                                            + ")Lorg/mozilla/javascript/Ref;";
                            cfw.addALoad(variableObjectLocal);
                            break;
                        default:
                            throw Kit.codeBug();
                    }
                    cfw.addPush(memberTypeFlags);
                    addScriptRuntimeInvoke(methodName, signature);
                }
                break;

            case Token.DOTQUERY:
                visitDotQuery(node, child);
                break;

            case Token.ESCXMLATTR:
                generateExpression(child, node);
                cfw.addALoad(contextLocal);
                addScriptRuntimeInvoke(
                        "escapeAttributeValue",
                        "(Ljava/lang/Object;"
                                + "Lorg/mozilla/javascript/Context;"
                                + ")Ljava/lang/String;");
                break;

            case Token.ESCXMLTEXT:
                generateExpression(child, node);
                cfw.addALoad(contextLocal);
                addScriptRuntimeInvoke(
                        "escapeTextValue",
                        "(Ljava/lang/Object;"
                                + "Lorg/mozilla/javascript/Context;"
                                + ")Ljava/lang/String;");
                break;

            case Token.DEFAULTNAMESPACE:
                generateExpression(child, node);
                cfw.addALoad(contextLocal);
                addScriptRuntimeInvoke(
                        "setDefaultNamespace",
                        "(Ljava/lang/Object;"
                                + "Lorg/mozilla/javascript/Context;"
                                + ")Ljava/lang/Object;");
                break;

            case Token.YIELD:
            case Token.YIELD_STAR:
                generateYieldPoint(node, true);
                break;

            case Token.WITHEXPR:
                {
                    Node with = child.getNext();
                    Node leaveWith = with.getNext();
                    generateStatement(child);
                    generateExpression(with.getFirstChild(), with);
                    generateStatement(leaveWith);
                    break;
                }

            case Token.ARRAYCOMP:
                {
                    Node expr = child.getNext();
                    generateStatement(child);
                    generateExpression(expr, node);
                    break;
                }

            case Token.TEMPLATE_LITERAL:
                visitTemplateLiteral(node);
                break;

            case Token.NULLISH_COALESCING:
                {
                    generateExpression(child, node); // left-hand side
                    int end = cfw.acquireLabel();

                    cfw.add(ByteCode.DUP);
                    addOptRuntimeInvoke("isNullOrUndefined", "(Ljava/lang/Object;)Z");
                    cfw.add(ByteCode.IFEQ, end);

                    cfw.add(ByteCode.POP);
                    generateExpression(child.getNext(), node); // right-hand side
                    cfw.markLabel(end);
                    break;
                }

            default:
                throw new RuntimeException("Unexpected node type " + type);
        }
    }

    private void finishGetElemGeneration(Node node, Node child) {
        generateExpression(child.getNext(), node); // id
        cfw.addALoad(contextLocal);
        cfw.addALoad(variableObjectLocal);

        if (node.getIntProp(Node.SUPER_PROPERTY_ACCESS, 0) == 1) {
            cfw.addALoad(thisObjLocal);
            addDynamicInvoke("PROP:GETELEMENTSUPER", Signatures.PROP_GET_ELEMENT_SUPER);
        } else {
            if (node.getIntProp(Node.ISNUMBER_PROP, -1) != -1) {
                addDynamicInvoke("PROP:GETINDEX", Signatures.PROP_GET_INDEX);
            } else {
                addDynamicInvoke("PROP:GETELEMENT", Signatures.PROP_GET_ELEMENT);
            }
        }
    }

    private void finishGetRefGeneration() {
        cfw.addALoad(contextLocal);
        addScriptRuntimeInvoke(
                "refGet",
                "(Lorg/mozilla/javascript/Ref;"
                        + "Lorg/mozilla/javascript/Context;"
                        + ")Ljava/lang/Object;");
    }

    private void finishRefSpecialGeneration(Node node) {
        String special = (String) node.getProp(Node.NAME_PROP);
        cfw.addPush(special);
        cfw.addALoad(contextLocal);
        cfw.addALoad(variableObjectLocal);
        addScriptRuntimeInvoke(
                "specialRef",
                "(Ljava/lang/Object;"
                        + "Ljava/lang/String;"
                        + "Lorg/mozilla/javascript/Context;"
                        + "Lorg/mozilla/javascript/Scriptable;"
                        + ")Lorg/mozilla/javascript/Ref;");
    }

    // Descend down the tree and return the first child that represents a yield
    // point, or null.
    private Node findNestedYield(Node node) {
        Node child = node.getFirstChild();
        while (child != null) {
            if ((child.getType() == Token.YIELD) || (child.getType() == Token.YIELD_STAR)) {
                return child;
            }
            Node grandchild = findNestedYield(child);
            if (grandchild != null) {
                return grandchild;
            }
            child = child.getNext();
        }
        return null;
    }

    private void generateYieldPoint(Node node, boolean exprContext) {
        if (unnestedYields.containsKey(node)) {
            // Yield was previously moved up via the "nestedYield" code below.
            if (exprContext) {
                String name = unnestedYields.get(node);
                cfw.addALoad(variableObjectLocal);
                cfw.addALoad(contextLocal);
                cfw.addALoad(variableObjectLocal);
                addDynamicInvoke("PROP:GETNOWARN:" + name, Signatures.PROP_GET_NOWARN);
            }
            return;
        }

        final Node nestedYield = findNestedYield(node);
        if (nestedYield != null) {
            // There is another "yield" nested below this one. Pull it up,
            // execute it now, and assign its result to a uniquely-named constant.
            // This is necessary because the recursive code to generate bytecode can't
            // handle a yield that uses the result of a nested yield.
            // Otherwise, expressions like "return yield(yield x)" return the wrong result.
            generateYieldPoint(nestedYield, true);

            final String nn = "__nested__yield__" + unnestedYieldCount;
            unnestedYieldCount++;
            cfw.addALoad(variableObjectLocal);
            cfw.add(ByteCode.SWAP);
            cfw.addALoad(contextLocal);
            cfw.addALoad(variableObjectLocal);
            addDynamicInvoke("PROP:SET:" + nn, Signatures.PROP_SET);
            cfw.add(ByteCode.POP);

            unnestedYields.put(nestedYield, nn);
        }

        // Now keep on as normal. When we encounter the nested yield, we will replace it
        // with a lookup of the constant.
        generateLocalYieldPoint(node, exprContext);
    }

    private void generateLocalYieldPoint(Node node, boolean exprContext) {

        // save stack state from the top to the bottom
        final int top = cfw.getStackTop();
        maxStack = maxStack > top ? maxStack : top;
        if (top != 0) {
            generateGetGeneratorStackState();
            for (int i = 0; i < top; i++) {
                cfw.add(ByteCode.DUP_X1);
                cfw.add(ByteCode.SWAP);
                cfw.addLoadConstant(i);
                cfw.add(ByteCode.SWAP);
                cfw.add(ByteCode.AASTORE);
            }
            // pop the array object
            cfw.add(ByteCode.POP);
        }

        // generate the yield argument
        Node child = node.getFirstChild();
        if (child != null) generateExpression(child, node);
        else Codegen.pushUndefined(cfw);

        if (node.getType() == Token.YIELD_STAR) {
            // We will replace the result with one that signifies we should have a generator
            cfw.add(ByteCode.NEW, "org/mozilla/javascript/ES6Generator$YieldStarResult");
            cfw.add(ByteCode.DUP_X1);
            cfw.add(ByteCode.SWAP);
            cfw.addInvoke(
                    ByteCode.INVOKESPECIAL,
                    "org/mozilla/javascript/ES6Generator$YieldStarResult",
                    "<init>",
                    "(Ljava/lang/Object;)V");
        }

        // change the resumption state
        int nextState = getNextGeneratorState(node);
        generateSetGeneratorResumptionPoint(nextState);

        boolean hasLocals = generateSaveLocals(node);

        cfw.add(ByteCode.ARETURN);

        // We get back here after a "next" on the generator object itself

        generateCheckForThrowOrClose(getTargetLabel(node), hasLocals, nextState);

        // reconstruct the stack from the bottom to the top
        if (top != 0) {
            generateGetGeneratorStackState();
            for (int i = (top - 1); i >= 0; i--) {
                cfw.add(ByteCode.DUP);
                cfw.addLoadConstant(i);
                cfw.add(ByteCode.AALOAD);
                cfw.add(ByteCode.SWAP);
            }
            cfw.add(ByteCode.POP);
        }

        // load return value from yield
        if (exprContext) {
            cfw.addALoad(argsLocal);
        }
    }

    private void generateCheckForThrowOrClose(int label, boolean hasLocals, int nextState) {
        int throwLabel = cfw.acquireLabel();
        int closeLabel = cfw.acquireLabel();

        // throw the user provided object, if the operation is .throw()
        cfw.markLabel(throwLabel);
        cfw.addALoad(argsLocal);
        generateThrowJavaScriptException();

        // throw our special internal exception if the generator is being closed
        cfw.markLabel(closeLabel);
        cfw.addALoad(argsLocal);
        cfw.add(ByteCode.CHECKCAST, "java/lang/Throwable");
        cfw.add(ByteCode.ATHROW);

        // mark the re-entry point
        // jump here after initializing the locals
        if (label != -1) cfw.markLabel(label);
        if (!hasLocals) {
            // jump here directly if there are no locals
            cfw.markTableSwitchCase(generatorSwitch, nextState);
        }

        // see if we need to dispatch for .close() or .throw()
        cfw.addILoad(operationLocal);
        cfw.addLoadConstant(NativeGenerator.GENERATOR_CLOSE);
        cfw.add(ByteCode.IF_ICMPEQ, closeLabel);
        cfw.addILoad(operationLocal);
        cfw.addLoadConstant(NativeGenerator.GENERATOR_THROW);
        cfw.add(ByteCode.IF_ICMPEQ, throwLabel);
    }

    private void visitTemplateLiteral(Node node) {
        // create the template literal call-site object for tagged template literals,
        // default template literals are already handled earlier in IRFactory
        int index = node.getExistingIntProp(Node.TEMPLATE_LITERAL_PROP);
        cfw.addALoad(contextLocal);
        cfw.addALoad(variableObjectLocal);
        cfw.add(
                ByteCode.GETSTATIC,
                codegen.mainClassName,
                codegen.getTemplateLiteralName(scriptOrFn),
                "[Ljava/lang/Object;");
        cfw.addPush(index);
        cfw.addInvoke(
                ByteCode.INVOKESTATIC,
                "org/mozilla/javascript/ScriptRuntime",
                "getTemplateLiteralCallSite",
                "(Lorg/mozilla/javascript/Context;"
                        + "Lorg/mozilla/javascript/Scriptable;"
                        + "[Ljava/lang/Object;I"
                        + ")Lorg/mozilla/javascript/Scriptable;");
    }

    private void generateIfJump(Node node, Node parent, int trueLabel, int falseLabel) {
        int type = node.getType();
        Node child = node.getFirstChild();

        switch (type) {
            case Token.NOT:
                generateIfJump(child, node, falseLabel, trueLabel);
                break;

            case Token.OR:
            case Token.AND:
                {
                    int interLabel = cfw.acquireLabel();
                    if (type == Token.AND) {
                        generateIfJump(child, node, interLabel, falseLabel);
                    } else {
                        generateIfJump(child, node, trueLabel, interLabel);
                    }
                    cfw.markLabel(interLabel);
                    child = child.getNext();
                    generateIfJump(child, node, trueLabel, falseLabel);
                    break;
                }

            case Token.IN:
            case Token.INSTANCEOF:
            case Token.LE:
            case Token.LT:
            case Token.GE:
            case Token.GT:
                visitIfJumpRelOp(node, child, trueLabel, falseLabel);
                break;

            case Token.EQ:
            case Token.NE:
            case Token.SHEQ:
            case Token.SHNE:
                visitIfJumpEqOp(node, child, trueLabel, falseLabel);
                break;

            default:
                // Generate generic code for non-optimized jump
                generateExpression(node, parent);
                addDynamicInvoke("MATH:TOBOOLEAN", Signatures.MATH_TO_BOOLEAN);
                cfw.add(ByteCode.IFNE, trueLabel);
                cfw.add(ByteCode.GOTO, falseLabel);
        }
    }

    private void visitFunction(OptFunctionNode ofn, int functionType) {
        int fnIndex = codegen.getIndex(ofn.fnode);
        cfw.add(ByteCode.NEW, Codegen.JSFUNCTION_CLASS_NAME);
        // Call function constructor
        cfw.add(ByteCode.DUP);
        cfw.addALoad(contextLocal);
        cfw.addALoad(variableObjectLocal);
        cfw.add(
                ByteCode.GETSTATIC,
                cfw.getClassName(),
                Codegen.DESCRIPTORS_FIELD_NAME,
                Codegen.DESCRIPTORS_FIELD_SIGNATURE);
        cfw.addPush(fnIndex);
        cfw.add(ByteCode.AALOAD);
        if (functionType == FunctionNode.ARROW_FUNCTION) {
            cfw.addALoad(thisObjLocal);
            cfw.addALoad(funObjLocal);
            cfw.addInvoke(
                    ByteCode.INVOKEVIRTUAL,
                    scriptOrFnClass,
                    "getHomeObject",
                    "()Lorg/mozilla/javascript/Scriptable;");
        } else if (ofn.fnode.isMethodDefinition()) {
            cfw.add(ByteCode.ACONST_NULL);
            cfw.addALoad(savedHomeObjectLocal);
        } else {
            cfw.add(ByteCode.ACONST_NULL);
            cfw.add(ByteCode.ACONST_NULL);
        }
        cfw.addInvoke(
                ByteCode.INVOKESPECIAL,
                Codegen.JSFUNCTION_CLASS_NAME,
                "<init>",
                Codegen.JSFUNCTION_CONSTRUCTOR_SIGNATURE);

        if (functionType == FunctionNode.FUNCTION_EXPRESSION
                || functionType == FunctionNode.ARROW_FUNCTION) {
            // Leave closure object on stack and do not pass it to
            // initFunction which suppose to connect statements to scope
            return;
        }
        cfw.addPush(functionType);
        cfw.addALoad(variableObjectLocal);
        cfw.addALoad(contextLocal); // load 'cx'
        addOptRuntimeInvoke(
                "initFunction",
                "(Lorg/mozilla/javascript/JSFunction;"
                        + "I"
                        + "Lorg/mozilla/javascript/Scriptable;"
                        + "Lorg/mozilla/javascript/Context;"
                        + ")V");
    }

    private int getTargetLabel(Node target) {
        int labelId = target.labelId();
        if (labelId == -1) {
            labelId = cfw.acquireLabel();
            target.labelId(labelId);
        }
        return labelId;
    }

    private void visitGoto(Jump node, int type, Node child) {
        Node target = node.target;
        if (type == Token.IFEQ || type == Token.IFNE) {
            if (child == null) throw Codegen.badTree();
            int targetLabel = getTargetLabel(target);
            int fallThruLabel = cfw.acquireLabel();
            if (type == Token.IFEQ) generateIfJump(child, node, targetLabel, fallThruLabel);
            else generateIfJump(child, node, fallThruLabel, targetLabel);
            cfw.markLabel(fallThruLabel);
        } else {
            if (type == Token.JSR) {
                if (isGenerator) {
                    addGotoWithReturn(target);
                } else {
                    // This assumes that JSR is only ever used for finally
                    inlineFinally(target);
                }
            } else {
                addGoto(target, ByteCode.GOTO);
            }
        }
    }

    private void addGotoWithReturn(Node target) {
        FinallyReturnPoint ret = finallys.get(target);
        cfw.addLoadConstant(ret.jsrPoints.size());
        addGoto(target, ByteCode.GOTO);
        // Don't leave something on the stack here!
        cfw.add(ByteCode.POP);
        int retLabel = cfw.acquireLabel();
        cfw.markLabel(retLabel);
        ret.jsrPoints.add(Integer.valueOf(retLabel));
    }

    private void generateArrayLiteralFactory(Node node, int count) {
        String methodName = codegen.getBodyMethodName(scriptOrFn) + "_literal" + count;
        initBodyGeneration();
        argsLocal = firstFreeLocal++;
        localsMax = firstFreeLocal;
        cfw.startMethod(
                methodName,
                "(Lorg/mozilla/javascript/Context;"
                        + scriptOrFnType
                        + "Ljava/lang/Object;"
                        + "Lorg/mozilla/javascript/Scriptable;"
                        + "Lorg/mozilla/javascript/Scriptable;"
                        + "[Ljava/lang/Object;"
                        + ")Lorg/mozilla/javascript/Scriptable;",
                (short) (ACC_STATIC | ACC_PRIVATE));
        visitArrayLiteral(node, node.getFirstChild(), true);
        cfw.add(ByteCode.ARETURN);
        cfw.stopMethod((short) (localsMax + 1));
    }

    private void generateObjectLiteralFactory(Node node, int count) {
        String methodName = codegen.getBodyMethodName(scriptOrFn) + "_literal" + count;
        initBodyGeneration();
        argsLocal = firstFreeLocal++;
        localsMax = firstFreeLocal;
        cfw.startMethod(
                methodName,
                "(Lorg/mozilla/javascript/Context;"
                        + scriptOrFnType
                        + "Ljava/lang/Object;"
                        + "Lorg/mozilla/javascript/Scriptable;"
                        + "Lorg/mozilla/javascript/Scriptable;"
                        + "[Ljava/lang/Object;"
                        + ")Lorg/mozilla/javascript/Scriptable;",
                (short) (ACC_STATIC | ACC_PRIVATE));
        visitObjectLiteral(node, node.getFirstChild(), true);
        cfw.add(ByteCode.ARETURN);
        cfw.stopMethod((short) (localsMax + 1));
    }

    private void visitArrayLiteral(Node node, Node child, boolean topLevel) {
        int numberOfSpread = node.getIntProp(Node.NUMBER_OF_SPREAD, 0);
        if (numberOfSpread > 0) {
            visitArrayLiteralWithSpread(node, child, numberOfSpread);
            return;
        }

        int count = countArguments(child);

        // If code budget is tight swap out literals into separate method
        if (!topLevel
                && (count > 10 || cfw.getCurrentCodeOffset() > 30000)
                && !hasVarsInRegs
                && !isGenerator
                && !inLocalBlock) {
            if (literals == null) {
                literals = new ArrayList<>();
            }
            literals.add(node);
            String methodName =
                    codegen.getBodyMethodName(scriptOrFn) + "_literal" + literals.size();
            cfw.addALoad(contextLocal);
            cfw.addALoad(funObjLocal);
            cfw.addALoad(newTargetLocal);
            cfw.addALoad(variableObjectLocal);
            cfw.addALoad(thisObjLocal);
            cfw.addALoad(argsLocal);
            cfw.addInvoke(
                    ByteCode.INVOKESTATIC,
                    codegen.mainClassName,
                    methodName,
                    "(Lorg/mozilla/javascript/Context;"
                            + scriptOrFnType
                            + "Ljava/lang/Object;"
                            + "Lorg/mozilla/javascript/Scriptable;"
                            + "Lorg/mozilla/javascript/Scriptable;"
                            + "[Ljava/lang/Object;"
                            + ")Lorg/mozilla/javascript/Scriptable;");
            return;
        }

        // load array to store array literal objects
        if (isGenerator) {
            // TODO: this is actually only necessary if the yield operation is
            // a child of this array or its children (bug 757410)
            for (int i = 0; i != count; ++i) {
                generateExpression(child, node);
                child = child.getNext();
            }
            addNewObjectArray(count);
            for (int i = 0; i != count; ++i) {
                cfw.add(ByteCode.DUP_X1);
                cfw.add(ByteCode.SWAP);
                cfw.addPush(count - i - 1);
                cfw.add(ByteCode.SWAP);
                cfw.add(ByteCode.AASTORE);
            }
        } else {
            addNewObjectArray(count);
            for (int i = 0; i != count; ++i) {
                cfw.add(ByteCode.DUP);
                cfw.addPush(i);
                generateExpression(child, node);
                cfw.add(ByteCode.AASTORE);
                child = child.getNext();
            }
        }
        int[] skipIndexes = (int[]) node.getProp(Node.SKIP_INDEXES_PROP);
        if (skipIndexes == null) {
            cfw.add(ByteCode.ACONST_NULL);
            cfw.add(ByteCode.ICONST_0);
        } else {
            cfw.addPush(OptRuntime.encodeIntArray(skipIndexes));
            cfw.addPush(skipIndexes.length);
        }
        cfw.addALoad(contextLocal);
        cfw.addALoad(variableObjectLocal);
        addOptRuntimeInvoke(
                "newArrayLiteral",
                "([Ljava/lang/Object;"
                        + "Ljava/lang/String;"
                        + "I"
                        + "Lorg/mozilla/javascript/Context;"
                        + "Lorg/mozilla/javascript/Scriptable;"
                        + ")Lorg/mozilla/javascript/Scriptable;");
    }

    private void visitArrayLiteralWithSpread(Node node, Node child, int numberOfSpread) {
        int count = countArguments(child);
        int[] skipIndexes = (int[]) node.getProp(Node.SKIP_INDEXES_PROP);

        // compute source positions if we have skip indexes
        int[] sourcePositions = null;
        if (skipIndexes != null) {
            sourcePositions = new int[count];
            int sourcePos = 0;
            int skipIdx = 0;
            for (int i = 0; i < count; i++) {
                while (skipIdx < skipIndexes.length && skipIndexes[skipIdx] == sourcePos) {
                    sourcePos++;
                    skipIdx++;
                }
                sourcePositions[i] = sourcePos;
                sourcePos++;
            }
        }

        // Create NewLiteralStorage for the array
        cfw.addALoad(contextLocal);
        cfw.addLoadConstant(count - numberOfSpread);
        cfw.addLoadConstant(0); // createKeys = false for arrays
        cfw.addInvoke(
                ByteCode.INVOKESTATIC,
                "org/mozilla/javascript/NewLiteralStorage",
                "create",
                "(Lorg/mozilla/javascript/Context;IZ)Lorg/mozilla/javascript/NewLiteralStorage;");

        // Set skip indexes if present
        if (skipIndexes != null) {
            cfw.add(ByteCode.DUP);
            cfw.addLoadConstant(skipIndexes.length);
            cfw.add(ByteCode.NEWARRAY, ByteCode.T_INT);
            for (int i = 0; i < skipIndexes.length; i++) {
                cfw.add(ByteCode.DUP);
                cfw.addLoadConstant(i);
                cfw.addLoadConstant(skipIndexes[i]);
                cfw.add(ByteCode.IASTORE);
            }
            cfw.addInvoke(
                    ByteCode.INVOKEVIRTUAL,
                    "org/mozilla/javascript/NewLiteralStorage",
                    "setSkipIndexes",
                    "([I)V");
        }

        // Process each element
        int childIdx = 0;
        while (child != null) {
            if (child.getType() == Token.DOTDOTDOT) {
                // Handle spread element: push the expression and call spread
                cfw.add(ByteCode.DUP);
                cfw.addALoad(contextLocal);
                cfw.addALoad(variableObjectLocal);
                generateExpression(child.getFirstChild(), node);
                // Push source position (0 if no skip indexes)
                if (skipIndexes != null) {
                    cfw.addLoadConstant(sourcePositions[childIdx]);
                } else {
                    cfw.addLoadConstant(0);
                }
                cfw.addInvoke(
                        ByteCode.INVOKEVIRTUAL,
                        "org/mozilla/javascript/NewLiteralStorage",
                        "spread",
                        "(Lorg/mozilla/javascript/Context;"
                                + "Lorg/mozilla/javascript/Scriptable;"
                                + "Ljava/lang/Object;"
                                + "I"
                                + ")V");
            } else {
                // Handle regular element: push the value
                cfw.add(ByteCode.DUP);
                generateExpression(child, node);
                cfw.addInvoke(
                        ByteCode.INVOKEVIRTUAL,
                        "org/mozilla/javascript/NewLiteralStorage",
                        "pushValue",
                        "(Ljava/lang/Object;)V");
            }
            child = child.getNext();
            childIdx++;
        }

        // Convert NewLiteralStorage to array
        int storageLocal = getNewWordLocal();
        cfw.addAStore(storageLocal);
        cfw.addALoad(storageLocal);
        cfw.addInvoke(
                ByteCode.INVOKEVIRTUAL,
                "org/mozilla/javascript/NewLiteralStorage",
                "getValues",
                "()[Ljava/lang/Object;");

        // Get adjusted skip indexes
        if (skipIndexes != null) {
            cfw.addALoad(storageLocal);
            cfw.addInvoke(
                    ByteCode.INVOKEVIRTUAL,
                    "org/mozilla/javascript/NewLiteralStorage",
                    "getAdjustedSkipIndexes",
                    "()[I");
        } else {
            cfw.add(ByteCode.ACONST_NULL); // skipIndexes
        }
        releaseWordLocal((short) storageLocal);

        cfw.addALoad(contextLocal);
        cfw.addALoad(variableObjectLocal);
        addOptRuntimeInvoke(
                "newArrayLiteral",
                "([Ljava/lang/Object;"
                        + "[I"
                        + "Lorg/mozilla/javascript/Context;"
                        + "Lorg/mozilla/javascript/Scriptable;"
                        + ")Lorg/mozilla/javascript/Scriptable;");
    }

    /** load two arrays with property ids and values */
    private void addLoadProperty(Node node, Node child, Object[] properties, int count) {
        cfw.addALoad(contextLocal);
        cfw.addLoadConstant(count - node.getIntProp(Node.NUMBER_OF_SPREAD, 0));
        cfw.addLoadConstant(1);
        cfw.addInvoke(
                ByteCode.INVOKESTATIC,
                "org/mozilla/javascript/NewLiteralStorage",
                "create",
                "(Lorg/mozilla/javascript/Context;IZ)Lorg/mozilla/javascript/NewLiteralStorage;");

        if (count == 0) {
            return;
        }

        if (isGenerator) {
            // Slightly more inefficient code, but necessary for bug 757410.
            // The problem is that, when we have a yield in a key or value,
            // we might actually suspend the function and resume it later.
            // To do that, we store all the stack's entries in
            // OptRuntime::GeneratorState::stackState, which is an Object[].
            // The class verifier would complain if we had a NewLiteralStorage
            // on the stack, and thus we add the various CHECKCAST to "trick"
            // the class verifier into thinking that we have only Object
            // instances. Also, we first push the key, _then_ the NewNativeStorage,
            // and then do a checkcast/swap/set value dance. It's slightly slower
            // than the base case, which is why we separated the two code paths.

            cfw.add(ByteCode.CHECKCAST, "java/lang/Object");
            short local = getNewWordLocal();
            cfw.addAStore(local);

            for (int i = 0; i < count; ++i) {
                if (child.getType() == Token.DOTDOTDOT) {
                    cfw.addALoad(contextLocal); // stack: [context]
                    cfw.addALoad(variableObjectLocal); // stack: [context, scope]
                    generateExpression(
                            child.getFirstChild(), node); // stack: [context, scope, sourceObj]
                    cfw.addALoad(local); // stack: [context, scope, sourceObj, store]
                    cfw.add(ByteCode.CHECKCAST, "org/mozilla/javascript/NewLiteralStorage");
                    cfw.add(ByteCode.SWAP); // stack: [context, scope, store, sourceObj]
                    cfw.addLoadConstant(0); // sourcePosition (unused for objects)
                    addOptRuntimeInvoke(
                            "spread",
                            "(Lorg/mozilla/javascript/Context;"
                                    + "Lorg/mozilla/javascript/Scriptable;"
                                    + "Lorg/mozilla/javascript/NewLiteralStorage;"
                                    + "Ljava/lang/Object;"
                                    + "I"
                                    + ")V"); // stack: []
                } else {
                    addLoadPropertyId(node, properties, i); // stack: [ki]
                    cfw.addALoad(local); // stack: [ki, store]
                    cfw.add(ByteCode.CHECKCAST, "org/mozilla/javascript/NewLiteralStorage");
                    cfw.add(ByteCode.SWAP); // stack: [store, ki]
                    cfw.addInvoke(
                            ByteCode.INVOKEVIRTUAL,
                            "org/mozilla/javascript/NewLiteralStorage",
                            "pushKey",
                            "(Ljava/lang/Object;)V"); // stack: []

                    addLoadPropertyValue(node, child);
                    cfw.addALoad(local);
                    cfw.add(ByteCode.CHECKCAST, "org/mozilla/javascript/NewLiteralStorage");
                    cfw.add(ByteCode.SWAP);
                    String methodName = getStoreMethodNameForLiteralProperty(child);
                    cfw.addInvoke(
                            ByteCode.INVOKEVIRTUAL,
                            "org/mozilla/javascript/NewLiteralStorage",
                            methodName,
                            "(Ljava/lang/Object;)V"); // Stack: [store]
                }

                child = child.getNext();
            }

            cfw.addALoad(local);
            cfw.add(ByteCode.CHECKCAST, "org/mozilla/javascript/NewLiteralStorage");
            releaseWordLocal(local);
        } else {
            for (int i = 0; i < count; ++i) {
                if (child.getType() == Token.DOTDOTDOT) {
                    cfw.add(ByteCode.DUP); // stack: [store, store]
                    cfw.addALoad(contextLocal); // stack: [store, store, context]
                    cfw.add(ByteCode.SWAP); // stack: [store, context, store]
                    cfw.addALoad(variableObjectLocal); // stack: [store, context, store, scope]
                    cfw.add(ByteCode.SWAP); // stack [store, context, scope, store]
                    generateExpression(
                            child.getFirstChild(),
                            node); // stack: [store, context, scope, store, sourceObj]
                    cfw.addLoadConstant(0); // sourcePosition (not used for objects)
                    addOptRuntimeInvoke(
                            "spread",
                            "(Lorg/mozilla/javascript/Context;"
                                    + "Lorg/mozilla/javascript/Scriptable;"
                                    + "Lorg/mozilla/javascript/NewLiteralStorage;"
                                    + "Ljava/lang/Object;"
                                    + "I"
                                    + ")V"); // stack: [store]
                } else {
                    cfw.add(ByteCode.DUP); // Stack: [store, store]
                    addLoadPropertyId(node, properties, i); // Stack: [store, store, Ki]
                    cfw.addInvoke(
                            ByteCode.INVOKEVIRTUAL,
                            "org/mozilla/javascript/NewLiteralStorage",
                            "pushKey",
                            "(Ljava/lang/Object;)V"); // Stack: [store]

                    cfw.add(ByteCode.DUP); // Stack: [store, store]
                    addLoadPropertyValue(node, child); // Stack: [store, store, Vi]
                    String methodName = getStoreMethodNameForLiteralProperty(child);
                    cfw.addInvoke(
                            ByteCode.INVOKEVIRTUAL,
                            "org/mozilla/javascript/NewLiteralStorage",
                            methodName,
                            "(Ljava/lang/Object;)V"); // Stack: [store]
                }

                child = child.getNext();
            }
        }

        // stack: [store]
    }

    private static String getStoreMethodNameForLiteralProperty(Node child) {
        switch (child.getType()) {
            case Token.GET:
                return "pushGetter";
            case Token.SET:
                return "pushSetter";
            default:
                return "pushValue";
        }
    }

    private void addLoadPropertyValue(Node node, Node child) {
        int childType = child.getType();
        if (childType == Token.GET || childType == Token.SET || childType == Token.METHOD) {
            generateExpression(child.getFirstChild(), node);
        } else {
            generateExpression(child, node);
        }
    }

    private void addLoadPropertyId(Node node, Object[] properties, int i) {
        Object id = properties[i];
        if (id instanceof Node) {
            // Will be a node of type Token.COMPUTED_PROPERTY wrapping the actual expression
            Node computedPropertyNode = (Node) id;
            generateExpression(computedPropertyNode.getFirstChild(), node);
        } else {
            if (id instanceof String) {
                cfw.addPush((String) id);
            } else {
                cfw.addPush(((Integer) id).intValue());
                addScriptRuntimeInvoke("wrapInt", "(I)Ljava/lang/Integer;");
            }
        }
    }

    private void visitObjectLiteral(Node node, Node child, boolean topLevel) {
        Object[] properties = (Object[]) node.getProp(Node.OBJECT_IDS_PROP);
        int count = properties == null ? 0 : properties.length;

        // If code budget is tight swap out literals into separate method
        if (!topLevel
                && (count > 10 || cfw.getCurrentCodeOffset() > 30000)
                && !hasVarsInRegs
                && !isGenerator
                && !inLocalBlock) {
            if (literals == null) {
                literals = new ArrayList<>();
            }
            literals.add(node);
            String methodName =
                    codegen.getBodyMethodName(scriptOrFn) + "_literal" + literals.size();
            cfw.addALoad(contextLocal);
            cfw.addALoad(funObjLocal);
            cfw.addALoad(newTargetLocal);
            cfw.addALoad(variableObjectLocal);
            cfw.addALoad(thisObjLocal);
            cfw.addALoad(argsLocal);
            cfw.addInvoke(
                    ByteCode.INVOKESTATIC,
                    codegen.mainClassName,
                    methodName,
                    "(Lorg/mozilla/javascript/Context;"
                            + scriptOrFnType
                            + "Ljava/lang/Object;"
                            + "Lorg/mozilla/javascript/Scriptable;"
                            + "Lorg/mozilla/javascript/Scriptable;"
                            + "[Ljava/lang/Object;"
                            + ")Lorg/mozilla/javascript/Scriptable;");
            return;
        }

        cfw.addALoad(contextLocal);
        cfw.addALoad(variableObjectLocal);
        cfw.addInvoke(
                ByteCode.INVOKEVIRTUAL,
                "org/mozilla/javascript/Context",
                "newObject",
                "(Lorg/mozilla/javascript/Scriptable;)Lorg/mozilla/javascript/Scriptable;");
        cfw.add(ByteCode.DUP);
        savedHomeObjectLocal = getNewWordLocal();
        cfw.addAStore(savedHomeObjectLocal);
        cfw.add(ByteCode.DUP);

        addLoadProperty(node, child, properties, count);

        // stack: [store]
        cfw.add(ByteCode.DUP); // stack: [store, store]
        cfw.addInvoke(
                ByteCode.INVOKEVIRTUAL,
                "org/mozilla/javascript/NewLiteralStorage",
                "getKeys",
                "()[Ljava/lang/Object;"); // stack: [store, keys]
        cfw.add(ByteCode.SWAP); // stack: [keys, store]
        cfw.add(ByteCode.DUP); // stack: [keys, store, store]
        cfw.addInvoke(
                ByteCode.INVOKEVIRTUAL,
                "org/mozilla/javascript/NewLiteralStorage",
                "getValues",
                "()[Ljava/lang/Object;"); // stack: [keys, store, values]
        cfw.add(ByteCode.SWAP); // stack: [keys, values, store]
        cfw.addInvoke(
                ByteCode.INVOKEVIRTUAL,
                "org/mozilla/javascript/NewLiteralStorage",
                "getGetterSetters",
                "()[I"); // stack: [keys, values, getterSetters]

        cfw.addALoad(contextLocal);
        cfw.addALoad(variableObjectLocal);
        addScriptRuntimeInvoke(
                "fillObjectLiteral",
                "("
                        + "Lorg/mozilla/javascript/Scriptable;"
                        + "[Ljava/lang/Object;"
                        + "[Ljava/lang/Object;"
                        + "[I"
                        + "Lorg/mozilla/javascript/Context;"
                        + "Lorg/mozilla/javascript/Scriptable;"
                        + ")V");
    }

    private void visitSpecialCall(Node node, int type, int specialType, Node child) {
        if (type == Token.NEW) {
            visitSpecialNewCall(node, specialType, child);
        } else if (node.getIntProp(Node.OPTIONAL_CHAINING, 0) == 1) {
            visitSpecialOptionalChainingCall(node, specialType, child);
        } else {
            visitSpecialNormalCall(node, specialType, child);
        }
    }

    private void visitSpecialNewCall(Node node, int specialType, Node child) {
        cfw.addALoad(contextLocal);
        generateExpression(child, node);
        child = child.getNext();
        generateCallArgArray(node, child, false);
        cfw.addALoad(variableObjectLocal);
        cfw.addPush(specialType);

        addScriptRuntimeInvoke(
                "newSpecial",
                "(Lorg/mozilla/javascript/Context;"
                        + "Ljava/lang/Object;"
                        + "[Ljava/lang/Object;"
                        + "Lorg/mozilla/javascript/Scriptable;"
                        + "I"
                        + ")Ljava/lang/Object;");
    }

    private void visitSpecialNormalCall(Node node, int specialType, Node child) {
        cfw.addALoad(contextLocal);
        generateFunctionAndThisObj(child, node);
        // stack: ... cx functionObj thisObj
        child = child.getNext();
        generateCallArgArray(node, child, false);
        cfw.addALoad(variableObjectLocal);
        cfw.addALoad(thisObjLocal);
        cfw.addPush(specialType);
        String sourceName = scriptOrFn.getSourceName();
        cfw.addPush(sourceName == null ? "" : sourceName);
        cfw.addPush(itsLineNumber);
        cfw.addPush(false);

        addScriptRuntimeInvoke(
                "callSpecial",
                "(Lorg/mozilla/javascript/Context;"
                        + "Lorg/mozilla/javascript/Callable;"
                        + "Lorg/mozilla/javascript/Scriptable;"
                        + "[Ljava/lang/Object;"
                        + "Lorg/mozilla/javascript/Scriptable;"
                        + "Lorg/mozilla/javascript/Scriptable;"
                        + "I"
                        + "Ljava/lang/String;IZ"
                        + ")Ljava/lang/Object;");
    }

    private void visitSpecialOptionalChainingCall(Node node, int specialType, Node child) {
        generateLookupResult(child, node);
        int afterLabel = cfw.acquireLabel();
        int doCallLabel = cfw.acquireLabel();
        cfw.add(ByteCode.DUP);
        addOptRuntimeInvoke("isNullOrUndefined", "(Ljava/lang/Object;)Z");
        cfw.add(ByteCode.IFEQ, doCallLabel);

        // result is null, so push undefined and jump to end
        cfw.add(ByteCode.POP);
        Codegen.pushUndefined(cfw);
        cfw.add(ByteCode.GOTO, afterLabel);

        // Make the call
        cfw.markLabel(doCallLabel);
        cfw.addALoad(contextLocal);
        cfw.add(ByteCode.SWAP);
        // stack: ... cx lookupResult
        popFunctionAndThis();
        // stack: ... cx functionObj thisObj

        child = child.getNext();
        generateCallArgArray(node, child, false);
        cfw.addALoad(variableObjectLocal);
        cfw.addALoad(thisObjLocal);
        cfw.addPush(specialType);
        String sourceName = scriptOrFn.getSourceName();
        cfw.addPush(sourceName == null ? "" : sourceName);
        cfw.addPush(itsLineNumber);
        cfw.addPush(true);

        addScriptRuntimeInvoke(
                "callSpecial",
                "(Lorg/mozilla/javascript/Context;"
                        + "Lorg/mozilla/javascript/Callable;"
                        + "Lorg/mozilla/javascript/Scriptable;"
                        + "[Ljava/lang/Object;"
                        + "Lorg/mozilla/javascript/Scriptable;"
                        + "Lorg/mozilla/javascript/Scriptable;"
                        + "I"
                        + "Ljava/lang/String;IZ"
                        + ")Ljava/lang/Object;");

        cfw.markLabel(afterLabel);
    }

    private void visitStandardCall(Node node, Node child) {
        assert node.getType() == Token.CALL;

        Node firstArgChild = child.getNext();
        Integer afterLabel = null;

        // Push a LookupResult or null to the stack
        generateLookupResult(child, node);

        if (node.getIntProp(Node.OPTIONAL_CHAINING, 0) == 1) {
            // Generate code to check for null lookup result
            afterLabel = cfw.acquireLabel();
            int doCallLabel = cfw.acquireLabel();

            cfw.add(ByteCode.DUP);
            addOptRuntimeInvoke("isNullOrUndefined", "(Ljava/lang/Object;)Z");
            cfw.add(ByteCode.IFEQ, doCallLabel);

            // If result is null, so return undefined and do nothing else
            cfw.add(ByteCode.POP);
            Codegen.pushUndefined(cfw);
            cfw.add(ByteCode.GOTO, afterLabel);

            cfw.markLabel(doCallLabel);
        }

        // We have to process the args now, and not later, because
        // that's what ECMAScript requires
        generateCallArgArray(node, firstArgChild, false);
        cfw.addAStore(argsLocal);

        if (node.getIntProp(Node.SUPER_PROPERTY_ACCESS, 0) == 1) {
            // Extract the function but ignore the "this" that comes back
            // in favor of the one that we've been saving up for.
            cfw.addInvoke(
                    ByteCode.INVOKEVIRTUAL,
                    "org.mozilla.javascript.ScriptRuntime$LookupResult",
                    "getCallable",
                    "()Lorg/mozilla/javascript/Callable;");
            cfw.addALoad(thisObjLocal);
        } else {
            popFunctionAndThis();
        }

        // stack: ... functionObj thisObj
        cfw.addALoad(contextLocal);
        cfw.add(ByteCode.SWAP);
        // stack: ... functionObj cx thisObj
        cfw.addALoad(variableObjectLocal);
        cfw.add(ByteCode.SWAP);
        // stack: ... functionObj cx scope thisObj
        cfw.addALoad(argsLocal);

        cfw.addInvoke(
                ByteCode.INVOKEINTERFACE,
                "org/mozilla/javascript/Callable",
                "call",
                "(Lorg/mozilla/javascript/Context;"
                        + "Lorg/mozilla/javascript/Scriptable;"
                        + "Lorg/mozilla/javascript/Scriptable;"
                        + "[Ljava/lang/Object;"
                        + ")Ljava/lang/Object;");

        if (afterLabel != null) {
            cfw.markLabel(afterLabel);
        }
    }

    private static int countArguments(Node firstArgChild) {
        int argCount = 0;
        for (Node arg = firstArgChild; arg != null; arg = arg.getNext()) {
            ++argCount;
        }
        return argCount;
    }

    private void visitStandardNew(Node node, Node child) {
        if (node.getType() != Token.NEW) throw Codegen.badTree();

        Node firstArgChild = child.getNext();

        generateExpression(child, node);
        // stack: ... functionObj
        cfw.addALoad(contextLocal);
        cfw.addALoad(variableObjectLocal);
        // stack: ... functionObj cx scope
        generateCallArgArray(node, firstArgChild, false);
        addScriptRuntimeInvoke(
                "newObject",
                "(Ljava/lang/Object;"
                        + "Lorg/mozilla/javascript/Context;"
                        + "Lorg/mozilla/javascript/Scriptable;"
                        + "[Ljava/lang/Object;"
                        + ")Lorg/mozilla/javascript/Scriptable;");
    }

    private void visitOptimizedCall(Node node, OptFunctionNode target, int type, Node child) {
        // Only calls to top-level functions (i.e. non member functions) can be optimized, so they
        // cannot be a super.foo() call
        if (node.getIntProp(Node.SUPER_PROPERTY_ACCESS, 0) == 1) Kit.codeBug();

        Node firstArgChild = child.getNext();
        String className = Codegen.JSFUNCTION_CLASS_NAME;

        short thisObjLocal = 0;
        if (type == Token.NEW) {
            generateExpression(child, node);
        } else {
            generateFunctionAndThisObj(child, node);
            thisObjLocal = getNewWordLocal();
            cfw.addAStore(thisObjLocal);
        }
        // stack: ... functionObj

        int beyond = cfw.acquireLabel();
        int regularCall = cfw.acquireLabel();

        // Check the target function is the one we want. We can do
        // this by comparing its descriptor to the one from our static
        // array of descriptors.
        cfw.add(ByteCode.DUP);
        cfw.add(ByteCode.INSTANCEOF, className);
        cfw.add(ByteCode.IFEQ, regularCall);
        cfw.add(ByteCode.CHECKCAST, className);
        cfw.add(ByteCode.DUP);
        cfw.addInvoke(
                ByteCode.INVOKEVIRTUAL,
                className,
                "getDescriptor",
                "()" + Codegen.DESCRIPTOR_CLASS_SIGNATURE);
        cfw.add(
                ByteCode.GETSTATIC,
                cfw.getClassName(),
                Codegen.DESCRIPTORS_FIELD_NAME,
                Codegen.DESCRIPTORS_FIELD_SIGNATURE);
        cfw.addPush(codegen.getIndex(target.fnode));
        cfw.add(ByteCode.AALOAD);
        cfw.add(ByteCode.IF_ACMPNE, regularCall);

        // stack: ... directFunct
        cfw.addALoad(contextLocal);
        cfw.add(ByteCode.SWAP);
        cfw.addALoad(newTargetLocal);
        cfw.addALoad(variableObjectLocal);
        // stack: ... cx directFunc new.target scope

        if (type == Token.NEW) {
            cfw.add(ByteCode.ACONST_NULL);
        } else {
            cfw.addALoad(thisObjLocal);
        }
        // stack: ... directFunc cx new.target scope thisObj
        /*
        Remember that directCall parameters are paired in 1 aReg and 1 dReg
        If the argument is an incoming arg, just pass the orginal pair thru.
        Else, if the argument is known to be typed 'Number', pass Void.TYPE
        in the aReg and the number is the dReg
        Else pass the JS object in the aReg and 0.0 in the dReg.
        */
        Node argChild = firstArgChild;
        while (argChild != null) {
            int dcp_register = nodeIsDirectCallParameter(argChild);
            if (dcp_register >= 0) {
                cfw.addALoad(dcp_register);
                cfw.addDLoad(dcp_register + 1);
            } else if (argChild.getIntProp(Node.ISNUMBER_PROP, -1) == Node.BOTH) {
                cfw.add(ByteCode.GETSTATIC, "java/lang/Void", "TYPE", "Ljava/lang/Class;");
                generateExpression(argChild, node);
            } else {
                generateExpression(argChild, node);
                cfw.addPush(0.0);
            }
            argChild = argChild.getNext();
        }

        cfw.add(
                ByteCode.GETSTATIC,
                "org/mozilla/javascript/ScriptRuntime",
                "emptyArgs",
                "[Ljava/lang/Object;");
        cfw.addInvoke(
                ByteCode.INVOKESTATIC,
                codegen.mainClassName,
                (type == Token.NEW)
                        ? codegen.getDirectCtorName(target.fnode)
                        : codegen.getBodyMethodName(target.fnode),
                codegen.getBodyMethodSignature(target.fnode));

        cfw.add(ByteCode.GOTO, beyond);

        cfw.markLabel(regularCall);
        // stack: ... functionObj
        cfw.addALoad(contextLocal);
        cfw.addALoad(variableObjectLocal);
        // stack: ... functionObj cx scope
        if (type != Token.NEW) {
            cfw.addALoad(thisObjLocal);
            releaseWordLocal(thisObjLocal);
            // stack: ... functionObj cx scope thisObj
        }
        // XXX: this will generate code for the child array the second time,
        // so expression code generation better not to alter tree structure...
        generateCallArgArray(node, firstArgChild, true);

        if (type == Token.NEW) {
            addScriptRuntimeInvoke(
                    "newObject",
                    "(Ljava/lang/Object;"
                            + "Lorg/mozilla/javascript/Context;"
                            + "Lorg/mozilla/javascript/Scriptable;"
                            + "[Ljava/lang/Object;"
                            + ")Lorg/mozilla/javascript/Scriptable;");
        } else {
            cfw.addInvoke(
                    ByteCode.INVOKEINTERFACE,
                    "org/mozilla/javascript/Callable",
                    "call",
                    "(Lorg/mozilla/javascript/Context;"
                            + "Lorg/mozilla/javascript/Scriptable;"
                            + "Lorg/mozilla/javascript/Scriptable;"
                            + "[Ljava/lang/Object;"
                            + ")Ljava/lang/Object;");
        }

        cfw.markLabel(beyond);
    }

    private void generateCallArgArray(Node node, Node argChild, boolean directCall) {
        int argCount = countArguments(argChild);
        // load array object to set arguments
        if (argCount == 1 && itsOneArgArray >= 0) {
            cfw.addALoad(itsOneArgArray);
        } else {
            addNewObjectArray(argCount);
        }
        // Copy arguments into it
        for (int i = 0; i != argCount; ++i) {
            // If we are compiling a generator an argument could be the result
            // of a yield. In that case we will have an immediate on the stack
            // which we need to avoid
            if (!isGenerator) {
                cfw.add(ByteCode.DUP);
                cfw.addPush(i);
            }

            if (!directCall) {
                generateExpression(argChild, node);
            } else {
                // If this has also been a directCall sequence, the Number
                // flag will have remained set for any parameter so that
                // the values could be copied directly into the outgoing
                // args. Here we want to force it to be treated as not in
                // a Number context, so we set the flag off.
                int dcp_register = nodeIsDirectCallParameter(argChild);
                if (dcp_register >= 0) {
                    dcpLoadAsObject(dcp_register);
                } else {
                    generateExpression(argChild, node);
                    int childNumberFlag = argChild.getIntProp(Node.ISNUMBER_PROP, -1);
                    if (childNumberFlag == Node.BOTH) {
                        addDoubleWrap();
                    }
                }
            }

            // When compiling generators, any argument to a method may be a
            // yield expression. Hence we compile the argument first and then
            // load the argument index and assign the value to the args array.
            if (isGenerator) {
                short tempLocal = getNewWordLocal();
                cfw.addAStore(tempLocal);
                cfw.add(ByteCode.CHECKCAST, "[Ljava/lang/Object;");
                cfw.add(ByteCode.DUP);
                cfw.addPush(i);
                cfw.addALoad(tempLocal);
                releaseWordLocal(tempLocal);
            }

            cfw.add(ByteCode.AASTORE);

            argChild = argChild.getNext();
        }
    }

    /**
     * Generate code to lookup a property or variable and push the function and this object to the
     * stack.
     */
    private void generateFunctionAndThisObj(Node node, Node parent) {
        generateLookupResult(node, parent);
        popFunctionAndThis();
    }

    /**
     * Generate code to look up a property or variable. The result is a ScriptRuntime.LookupResult
     * object on the stack.
     */
    private void generateLookupResult(Node node, Node parent) {
        // Place on stack (function object, function this) pair
        int type = node.getType();
        boolean isOptionalChainingCall = parent.getIntProp(Node.OPTIONAL_CHAINING, 0) == 1;
        switch (node.getType()) {
            case Token.GETPROPNOWARN:
                throw Kit.codeBug();

            case Token.GETPROP:
            case Token.GETELEM:
                {
                    Node target = node.getFirstChild();
                    generateExpression(target, node);
                    Node id = target.getNext();
                    if (type == Token.GETPROP) {
                        String property = id.getString();
                        cfw.addALoad(contextLocal);
                        cfw.addALoad(variableObjectLocal);
                        String propAccessType =
                                isOptionalChainingCall ? "GETWITHTHISOPTIONAL" : "GETWITHTHIS";
                        addDynamicInvoke(
                                "PROP:" + propAccessType + ":" + property,
                                Signatures.PROP_GET_THIS);
                    } else {
                        generateExpression(id, node); // id
                        if (node.getIntProp(Node.ISNUMBER_PROP, -1) != -1) addDoubleWrap();
                        cfw.addALoad(contextLocal);
                        cfw.addALoad(variableObjectLocal);
                        String name =
                                isOptionalChainingCall
                                        ? "getElemAndThisOptional"
                                        : "getElemAndThis";
                        addScriptRuntimeInvoke(
                                name,
                                "(Ljava/lang/Object;"
                                        + "Ljava/lang/Object;"
                                        + "Lorg/mozilla/javascript/Context;"
                                        + "Lorg/mozilla/javascript/Scriptable;"
                                        + ")Lorg/mozilla/javascript/ScriptRuntime$LookupResult;");
                    }
                    break;
                }

            case Token.NAME:
                {
                    String name = node.getString();
                    cfw.addALoad(variableObjectLocal);
                    cfw.addALoad(contextLocal);
                    String propAccessType =
                            isOptionalChainingCall ? "GETWITHTHISOPTIONAL" : "GETWITHTHIS";
                    addDynamicInvoke(
                            "NAME:" + propAccessType + ":" + name, Signatures.NAME_GET_THIS);
                    break;
                }

            default: // including GETVAR
                {
                    generateExpression(node, parent);
                    cfw.addALoad(contextLocal);
                    String name =
                            isOptionalChainingCall ? "getValueAndThisOptional" : "getValueAndThis";
                    addScriptRuntimeInvoke(
                            name,
                            "(Ljava/lang/Object;"
                                    + "Lorg/mozilla/javascript/Context;"
                                    + ")Lorg/mozilla/javascript/ScriptRuntime$LookupResult;");
                    break;
                }
        }
    }

    /**
     * Assume that a LookupResult is at the top of the stack, and push the callable and this object.
     */
    private void popFunctionAndThis() {
        cfw.add(ByteCode.DUP);
        cfw.addInvoke(
                ByteCode.INVOKEVIRTUAL,
                "org.mozilla.javascript.ScriptRuntime$LookupResult",
                "getCallable",
                "()Lorg/mozilla/javascript/Callable;");
        cfw.add(ByteCode.SWAP);
        cfw.addInvoke(
                ByteCode.INVOKEVIRTUAL,
                "org.mozilla.javascript.ScriptRuntime$LookupResult",
                "getThis",
                "()Lorg/mozilla/javascript/Scriptable;");
    }

    private void updateLineNumber(Node node) {
        itsLineNumber = node.getLineno();
        if (itsLineNumber == -1) return;
        cfw.addLineNumberEntry((short) itsLineNumber);
    }

    private void visitTryCatchFinally(Jump node, Node child) {
        /* Save the variable object, in case there are with statements
         * enclosed by the try block and we catch some exception.
         * We'll restore it for the catch block so that catch block
         * statements get the right scope.
         */

        // OPT we only need to do this if there are enclosed WITH
        // statements; could statically check and omit this if there aren't any.

        // XXX OPT Maybe instead do syntactic transforms to associate
        // each 'with' with a try/finally block that does the exitwith.

        short savedVariableObject = getNewWordLocal();
        cfw.addALoad(variableObjectLocal);
        cfw.addAStore(savedVariableObject);

        /*
         * Generate the code for the tree; most of the work is done in IRFactory
         * and NodeTransformer;  Codegen just adds the java handlers for the
         * javascript catch and finally clauses.  */

        int startLabel = cfw.acquireLabel();
        cfw.markLabel(startLabel, 0);

        Node catchTarget = node.target;
        Node finallyTarget = node.getFinally();
        int[] handlerLabels = new int[EXCEPTION_MAX];

        exceptionManager.pushExceptionInfo(node);
        if (catchTarget != null) {
            handlerLabels[JAVASCRIPT_EXCEPTION] = cfw.acquireLabel();
            handlerLabels[EVALUATOR_EXCEPTION] = cfw.acquireLabel();
            handlerLabels[ECMAERROR_EXCEPTION] = cfw.acquireLabel();
            Context cx = Context.getCurrentContext();
            if (cx != null && cx.hasFeature(Context.FEATURE_ENHANCED_JAVA_ACCESS)) {
                handlerLabels[THROWABLE_EXCEPTION] = cfw.acquireLabel();
            }
        }
        if (finallyTarget != null) {
            handlerLabels[FINALLY_EXCEPTION] = cfw.acquireLabel();
        }
        exceptionManager.setHandlers(handlerLabels, startLabel);

        // create a table for the equivalent of JSR returns
        if (isGenerator && finallyTarget != null) {
            FinallyReturnPoint ret = new FinallyReturnPoint();
            if (finallys == null) {
                finallys = new HashMap<>();
            }
            // add the finally target to hashtable
            finallys.put(finallyTarget, ret);
            // add the finally node as well to the hash table
            finallys.put(finallyTarget.getNext(), ret);
        }

        while (child != null) {
            if (child == catchTarget) {
                int catchLabel = getTargetLabel(catchTarget);
                exceptionManager.removeHandler(JAVASCRIPT_EXCEPTION, catchLabel);
                exceptionManager.removeHandler(EVALUATOR_EXCEPTION, catchLabel);
                exceptionManager.removeHandler(ECMAERROR_EXCEPTION, catchLabel);
                exceptionManager.removeHandler(THROWABLE_EXCEPTION, catchLabel);
            }
            generateStatement(child);
            child = child.getNext();
        }

        // control flow skips the handlers
        int realEnd = cfw.acquireLabel();
        cfw.add(ByteCode.GOTO, realEnd);

        int exceptionLocal = getLocalBlockRegister(node);
        // javascript handler; unwrap exception and GOTO to javascript
        // catch area.
        if (catchTarget != null) {
            // get the label to goto
            int catchLabel = catchTarget.labelId();

            // If the function is a generator, then handlerLabels will consist
            // of zero labels. generateCatchBlock will create its own label
            // in this case. The extra parameter for the label is added for
            // the case of non-generator functions that inline finally blocks.

            generateCatchBlock(
                    JAVASCRIPT_EXCEPTION,
                    savedVariableObject,
                    catchLabel,
                    exceptionLocal,
                    handlerLabels[JAVASCRIPT_EXCEPTION]);
            /*
             * catch WrappedExceptions, see if they are wrapped
             * JavaScriptExceptions. Otherwise, rethrow.
             */
            generateCatchBlock(
                    EVALUATOR_EXCEPTION,
                    savedVariableObject,
                    catchLabel,
                    exceptionLocal,
                    handlerLabels[EVALUATOR_EXCEPTION]);

            /*
                we also need to catch EcmaErrors and feed the
                associated error object to the handler
            */
            generateCatchBlock(
                    ECMAERROR_EXCEPTION,
                    savedVariableObject,
                    catchLabel,
                    exceptionLocal,
                    handlerLabels[ECMAERROR_EXCEPTION]);

            Context cx = Context.getCurrentContext();
            if (cx != null && cx.hasFeature(Context.FEATURE_ENHANCED_JAVA_ACCESS)) {
                generateCatchBlock(
                        THROWABLE_EXCEPTION,
                        savedVariableObject,
                        catchLabel,
                        exceptionLocal,
                        handlerLabels[THROWABLE_EXCEPTION]);
            }
        }

        // finally handler; catch all exceptions, store to a local; JSR to
        // the finally, then re-throw.
        if (finallyTarget != null) {
            int finallyHandler = cfw.acquireLabel();
            int finallyEnd = cfw.acquireLabel();
            cfw.markHandler(finallyHandler);
            if (!isGenerator) {
                cfw.markLabel(handlerLabels[FINALLY_EXCEPTION]);
            }
            cfw.addAStore(exceptionLocal);

            // reset the variable object local
            cfw.addALoad(savedVariableObject);
            cfw.addAStore(variableObjectLocal);

            // get the label to JSR to
            int finallyLabel = finallyTarget.labelId();
            if (isGenerator) addGotoWithReturn(finallyTarget);
            else {
                inlineFinally(finallyTarget, handlerLabels[FINALLY_EXCEPTION], finallyEnd);
            }

            // rethrow
            cfw.addALoad(exceptionLocal);
            if (isGenerator) cfw.add(ByteCode.CHECKCAST, "java/lang/Throwable");
            cfw.add(ByteCode.ATHROW);

            cfw.markLabel(finallyEnd);
            // mark the handler
            if (isGenerator) {
                cfw.addExceptionHandler(
                        startLabel, finallyLabel, finallyHandler, null); // catch any
            }
        }
        releaseWordLocal(savedVariableObject);
        cfw.markLabel(realEnd);

        if (!isGenerator) {
            exceptionManager.popExceptionInfo();
        }
    }

    private static final int JAVASCRIPT_EXCEPTION = 0;
    private static final int EVALUATOR_EXCEPTION = 1;
    private static final int ECMAERROR_EXCEPTION = 2;
    private static final int THROWABLE_EXCEPTION = 3;
    // Finally catch-alls are technically Throwable, but we want a distinction
    // for the exception manager and we want to use a null string instead of
    // an explicit Throwable string.
    private static final int FINALLY_EXCEPTION = 4;
    private static final int EXCEPTION_MAX = 5;

    private void generateCatchBlock(
            int exceptionType,
            short savedVariableObject,
            int catchLabel,
            int exceptionLocal,
            int handler) {
        if (handler == 0) {
            handler = cfw.acquireLabel();
        }
        cfw.markHandler(handler);

        // MS JVM gets cranky if the exception object is left on the stack
        cfw.addAStore(exceptionLocal);

        // reset the variable object local
        cfw.addALoad(savedVariableObject);
        cfw.addAStore(variableObjectLocal);

        cfw.add(ByteCode.GOTO, catchLabel);
    }

    private static String exceptionTypeToName(int exceptionType) {
        if (exceptionType == JAVASCRIPT_EXCEPTION) {
            return "org/mozilla/javascript/JavaScriptException";
        } else if (exceptionType == EVALUATOR_EXCEPTION) {
            return "org/mozilla/javascript/EvaluatorException";
        } else if (exceptionType == ECMAERROR_EXCEPTION) {
            return "org/mozilla/javascript/EcmaError";
        } else if (exceptionType == THROWABLE_EXCEPTION) {
            return "java/lang/Throwable";
        } else if (exceptionType == FINALLY_EXCEPTION) {
            return null;
        } else {
            throw Kit.codeBug();
        }
    }

    /**
     * Manages placement of exception handlers for non-generator functions.
     *
     * <p>For generator functions, there are mechanisms put into place to emulate jsr by using a
     * goto with a return label. That is one mechanism for implementing finally blocks. The other,
     * which is implemented by Sun, involves duplicating the finally block where jsr instructions
     * would normally be. However, inlining finally blocks causes problems with translating
     * exception handlers. Instead of having one big bytecode range for each exception, we now have
     * to skip over the inlined finally blocks. This class is meant to help implement this.
     *
     * <p>Every time a try block is encountered during translation, exception information should be
     * pushed into the manager, which is treated as a stack. The addHandler() and setHandlers()
     * methods may be used to register exceptionHandlers for the try block; removeHandler() is used
     * to reverse the operation. At the end of the try/catch/finally, the exception state for it
     * should be popped.
     *
     * <p>The important function here is markInlineFinally. This finds which finally block on the
     * exception state stack is being inlined and skips the proper exception handlers until the
     * finally block is generated.
     */
    private class ExceptionManager {
        ExceptionManager() {
            exceptionInfo = new ArrayDeque<>();
        }

        /**
         * Push a new try block onto the exception information stack.
         *
         * @param node an exception handling node (node.getType() == Token.TRY)
         */
        void pushExceptionInfo(Jump node) {
            Node fBlock = getFinallyAtTarget(node.getFinally());
            ExceptionInfo ei = new ExceptionInfo(node, fBlock);
            exceptionInfo.add(ei);
        }

        /**
         * Register an exception handler for the try block at the top of the exception information
         * stack.
         *
         * @param exceptionType one of the integer constants representing an exception type
         * @param handlerLabel the label of the exception handler
         * @param startLabel the label where the exception handling begins
         */
        void addHandler(int exceptionType, int handlerLabel, int startLabel) {
            ExceptionInfo top = getTop();
            top.handlerLabels[exceptionType] = handlerLabel;
            top.exceptionStarts[exceptionType] = startLabel;
        }

        /**
         * Register multiple exception handlers for the top try block. If the exception type maps to
         * a zero label, then it is ignored.
         *
         * @param handlerLabels a map from integer constants representing an exception type to the
         *     label of the exception handler
         * @param startLabel the label where all of the exception handling begins
         */
        void setHandlers(int[] handlerLabels, int startLabel) {
            for (int i = 0; i < handlerLabels.length; i++) {
                if (handlerLabels[i] != 0) {
                    addHandler(i, handlerLabels[i], startLabel);
                }
            }
        }

        /**
         * Remove an exception handler for the top try block.
         *
         * @param exceptionType one of the integer constants representing an exception type
         * @param endLabel a label representing the end of the last bytecode that should be handled
         *     by the exception
         * @returns the label of the exception handler associated with the exception type
         */
        int removeHandler(int exceptionType, int endLabel) {
            ExceptionInfo top = getTop();
            if (top.handlerLabels[exceptionType] != 0) {
                int handlerLabel = top.handlerLabels[exceptionType];
                endCatch(top, exceptionType, endLabel);
                top.handlerLabels[exceptionType] = 0;
                return handlerLabel;
            }
            return 0;
        }

        /** Remove the top try block from the exception information stack. */
        void popExceptionInfo() {
            exceptionInfo.removeLast();
        }

        /**
         * Mark the start of an inlined finally block.
         *
         * <p>When a finally block is inlined, any exception handlers that are lexically inside of
         * its try block should not cover the range of the exception block. We scan from the
         * innermost try block outward until we find the try block that matches the finally block.
         * For any block whose exception handlers that aren't currently stopped by a finally block,
         * we stop the handlers at the beginning of the finally block and set it as the finally
         * block that has stopped the handlers. This prevents other inlined finally blocks from
         * prematurely ending skip ranges and creating bad exception handler ranges.
         *
         * @param finallyBlock the finally block that is being inlined
         * @param finallyStart the label of the beginning of the inlined code
         */
        void markInlineFinallyStart(Node finallyBlock, int finallyStart) {
            // Traverse the stack in LIFO order until the try block
            // corresponding to the finally block has been reached. We must
            // traverse backwards because the earlier exception handlers in
            // the exception handler table have priority when determining which
            // handler to use. Therefore, we start with the most nested try
            // block and move outward.
            Iterator<ExceptionInfo> iter = exceptionInfo.descendingIterator();
            while (iter.hasNext()) {
                ExceptionInfo ei = iter.next();
                for (int i = 0; i < EXCEPTION_MAX; i++) {
                    if (ei.handlerLabels[i] != 0 && ei.currentFinally == null) {
                        endCatch(ei, i, finallyStart);
                        ei.exceptionStarts[i] = 0;
                        ei.currentFinally = finallyBlock;
                    }
                }
                if (ei.finallyBlock == finallyBlock) {
                    break;
                }
            }
        }

        /**
         * Mark the end of an inlined finally block.
         *
         * <p>For any set of exception handlers that have been stopped by the inlined block, resume
         * exception handling at the end of the finally block.
         *
         * @param finallyBlock the finally block that is being inlined
         * @param finallyEnd the label of the end of the inlined code
         */
        void markInlineFinallyEnd(Node finallyBlock, int finallyEnd) {
            Iterator<ExceptionInfo> iter = exceptionInfo.descendingIterator();
            while (iter.hasNext()) {
                ExceptionInfo ei = iter.next();
                for (int i = 0; i < EXCEPTION_MAX; i++) {
                    if (ei.handlerLabels[i] != 0 && ei.currentFinally == finallyBlock) {
                        ei.exceptionStarts[i] = finallyEnd;
                        ei.currentFinally = null;
                    }
                }
                if (ei.finallyBlock == finallyBlock) {
                    break;
                }
            }
        }

        /**
         * Mark off the end of a bytecode chunk that should be handled by an exceptionHandler.
         *
         * <p>The caller of this method must appropriately mark the start of the next bytecode chunk
         * or remove the handler.
         */
        private void endCatch(ExceptionInfo ei, int exceptionType, int catchEnd) {
            if (ei.exceptionStarts[exceptionType] == 0) {
                throw new IllegalStateException("bad exception start");
            }

            int currentStart = ei.exceptionStarts[exceptionType];
            int currentStartPC = cfw.getLabelPC(currentStart);
            int catchEndPC = cfw.getLabelPC(catchEnd);
            if (currentStartPC != catchEndPC) {
                cfw.addExceptionHandler(
                        ei.exceptionStarts[exceptionType],
                        catchEnd,
                        ei.handlerLabels[exceptionType],
                        exceptionTypeToName(exceptionType));
            }
        }

        private ExceptionInfo getTop() {
            return exceptionInfo.getLast();
        }

        private class ExceptionInfo {
            ExceptionInfo(Jump node, Node finallyBlock) {
                this.finallyBlock = finallyBlock;
                handlerLabels = new int[EXCEPTION_MAX];
                exceptionStarts = new int[EXCEPTION_MAX];
                currentFinally = null;
            }

            Node finallyBlock;
            int[] handlerLabels;
            int[] exceptionStarts;
            // The current finally block that has temporarily ended the
            // exception handler ranges
            Node currentFinally;
        }

        // A stack of try/catch block information ordered by lexical scoping
        private ArrayDeque<ExceptionInfo> exceptionInfo;
    }

    private ExceptionManager exceptionManager = new ExceptionManager();

    /**
     * Inline a FINALLY node into the method bytecode.
     *
     * <p>This method takes a label that points to the real start of the finally block as
     * implemented in the bytecode. This is because in some cases, the finally block really starts
     * before any of the code in the Node. For example, the catch-all-rethrow finally block has a
     * few instructions prior to the finally block made by the user.
     *
     * <p>In addition, an end label that should be unmarked is given as a method parameter. It is
     * the responsibility of any callers of this method to mark the label.
     *
     * <p>The start and end labels of the finally block are used to exclude the inlined block from
     * the proper exception handler. For example, an inlined finally block should not be handled by
     * a catch-all-rethrow.
     *
     * @param finallyTarget a TARGET node directly preceding a FINALLY node or a FINALLY node itself
     * @param finallyStart a pre-marked label that indicates the actual start of the finally block
     *     in the bytecode.
     * @param finallyEnd an unmarked label that will indicate the actual end of the finally block in
     *     the bytecode.
     */
    private void inlineFinally(Node finallyTarget, int finallyStart, int finallyEnd) {
        Node fBlock = getFinallyAtTarget(finallyTarget);
        fBlock.resetTargets();
        Node child = fBlock.getFirstChild();
        exceptionManager.markInlineFinallyStart(fBlock, finallyStart);
        while (child != null) {
            generateStatement(child);
            child = child.getNext();
        }
        exceptionManager.markInlineFinallyEnd(fBlock, finallyEnd);
    }

    private void inlineFinally(Node finallyTarget) {
        int finallyStart = cfw.acquireLabel();
        int finallyEnd = cfw.acquireLabel();
        cfw.markLabel(finallyStart);
        inlineFinally(finallyTarget, finallyStart, finallyEnd);
        cfw.markLabel(finallyEnd);
    }

    /**
     * Get a FINALLY node at a point in the IR.
     *
     * <p>This is strongly dependent on the generated IR. If the node is a TARGET, it only checks
     * the next node to see if it is a FINALLY node.
     */
    private static Node getFinallyAtTarget(Node node) {
        if (node == null) {
            return null;
        }
        if (node.getType() == Token.FINALLY) {
            return node;
        }
        if (node.getType() == Token.TARGET) {
            Node fBlock = node.getNext();
            if (fBlock != null && fBlock.getType() == Token.FINALLY) {
                return fBlock;
            }
        }
        throw Kit.codeBug("bad finally target");
    }

    private boolean generateSaveLocals(Node node) {
        int count = 0;
        for (int i = 0; i < firstFreeLocal; i++) {
            if (locals[i] != 0) count++;
        }

        if (count == 0) {
            ((FunctionNode) scriptOrFn).addLiveLocals(node, null);
            return false;
        }

        // calculate the max locals
        maxLocals = maxLocals > count ? maxLocals : count;

        // create a locals list
        int[] ls = new int[count];
        int s = 0;
        for (int i = 0; i < firstFreeLocal; i++) {
            if (locals[i] != 0) {
                ls[s] = i;
                s++;
            }
        }

        // save the locals
        ((FunctionNode) scriptOrFn).addLiveLocals(node, ls);

        // save locals
        generateGetGeneratorLocalsState();
        for (int i = 0; i < count; i++) {
            cfw.add(ByteCode.DUP);
            cfw.addLoadConstant(i);
            cfw.addALoad(ls[i]);
            cfw.add(ByteCode.AASTORE);
        }
        // pop the array off the stack
        cfw.add(ByteCode.POP);

        return true;
    }

    private void visitSwitch(Jump switchNode, Node child) {
        // See comments in IRFactory.createSwitch() for description
        // of SWITCH node

        generateExpression(child, switchNode);
        // save selector value
        short selector = getNewWordLocal();
        cfw.addAStore(selector);

        for (Jump caseNode = (Jump) child.getNext();
                caseNode != null;
                caseNode = (Jump) caseNode.getNext()) {
            if (caseNode.getType() != Token.CASE) throw Codegen.badTree();
            Node test = caseNode.getFirstChild();
            generateExpression(test, caseNode);
            cfw.addALoad(selector);
            addDynamicInvoke("MATH:SHALLOWEQ", Signatures.MATH_SHALLOW_EQ);
            addGoto(caseNode.target, ByteCode.IFNE);
        }
        releaseWordLocal(selector);
    }

    private void visitTypeofname(Node node) {
        if (hasVarsInRegs) {
            int varIndex = fnCurrent.fnode.getIndexForNameNode(node);
            if (varIndex >= 0) {
                if (fnCurrent.isNumberVar(varIndex)) {
                    cfw.addPush("number");
                } else if (varIsDirectCallParameter(varIndex)) {
                    int dcp_register = varRegisters[varIndex];
                    cfw.addALoad(dcp_register);
                    cfw.add(ByteCode.GETSTATIC, "java/lang/Void", "TYPE", "Ljava/lang/Class;");
                    int isNumberLabel = cfw.acquireLabel();
                    cfw.add(ByteCode.IF_ACMPEQ, isNumberLabel);
                    int stack = cfw.getStackTop();
                    cfw.addALoad(dcp_register);
                    addScriptRuntimeInvoke("typeof", "(Ljava/lang/Object;" + ")Ljava/lang/String;");
                    int beyond = cfw.acquireLabel();
                    cfw.add(ByteCode.GOTO, beyond);
                    cfw.markLabel(isNumberLabel, stack);
                    cfw.addPush("number");
                    cfw.markLabel(beyond);
                } else {
                    cfw.addALoad(varRegisters[varIndex]);
                    addScriptRuntimeInvoke("typeof", "(Ljava/lang/Object;" + ")Ljava/lang/String;");
                }
                return;
            }
        }
        cfw.addALoad(variableObjectLocal);
        cfw.addPush(node.getString());
        addScriptRuntimeInvoke(
                "typeofName",
                "(Lorg/mozilla/javascript/Scriptable;"
                        + "Ljava/lang/String;"
                        + ")Ljava/lang/String;");
    }

    /**
     * Save the current code offset. This saved code offset is used to compute instruction counts in
     * subsequent calls to {@link #addInstructionCount()}.
     */
    private void saveCurrentCodeOffset() {
        savedCodeOffset = cfw.getCurrentCodeOffset();
    }

    /**
     * Generate calls to ScriptRuntime.addInstructionCount to keep track of executed instructions
     * and call {@code observeInstructionCount()} if a threshold is exceeded.<br>
     * Calculates the count from getCurrentCodeOffset - savedCodeOffset
     */
    private void addInstructionCount() {
        int count = cfw.getCurrentCodeOffset() - savedCodeOffset;
        // TODO we used to return for count == 0 but that broke the following:
        //    while(true) continue; (see bug 531600)
        // To be safe, we now always count at least 1 instruction when invoked.
        addInstructionCount(Math.max(count, 1));
    }

    /**
     * Generate calls to ScriptRuntime.addInstructionCount to keep track of executed instructions
     * and call {@code observeInstructionCount()} if a threshold is exceeded.<br>
     * Takes the count as a parameter - used to add monitoring to loops and other blocks that don't
     * have any ops - this allows for monitoring/killing of while(true) loops and such.
     */
    private void addInstructionCount(int count) {
        cfw.addALoad(contextLocal);
        cfw.addPush(count);
        addScriptRuntimeInvoke("addInstructionCount", "(Lorg/mozilla/javascript/Context;" + "I)V");
    }

    private void visitIncDec(Node node) {
        int incrDecrMask = node.getExistingIntProp(Node.INCRDECR_PROP);
        Node child = node.getFirstChild();

        if (child.getIntProp(Node.SUPER_PROPERTY_ACCESS, 0) == 1) {
            visitSuperIncDec(node, child, incrDecrMask);
            return;
        }

        switch (child.getType()) {
            case Token.GETVAR:
                if (!hasVarsInRegs) Kit.codeBug();
                boolean post = ((incrDecrMask & Node.POST_FLAG) != 0);
                int varIndex = fnCurrent.getVarIndex(child);
                int reg = varRegisters[varIndex];
                boolean[] constDeclarations = fnCurrent.fnode.getParamAndVarConst();
                if (constDeclarations[varIndex]) {
                    if (node.getIntProp(Node.ISNUMBER_PROP, -1) != -1) {
                        int offset = varIsDirectCallParameter(varIndex) ? 1 : 0;
                        cfw.addDLoad(reg + offset);
                        if (!post) {
                            cfw.addPush(1.0);
                            if ((incrDecrMask & Node.DECR_FLAG) == 0) {
                                cfw.add(ByteCode.DADD);
                            } else {
                                cfw.add(ByteCode.DSUB);
                            }
                        }
                    } else {
                        if (varIsDirectCallParameter(varIndex)) {
                            dcpLoadAsObject(reg);
                        } else {
                            cfw.addALoad(reg);
                        }
                        if (post) {
                            cfw.add(ByteCode.DUP);
                            addObjectToDouble();
                            cfw.add(ByteCode.POP2);
                        } else {
                            addObjectToDouble();
                            cfw.addPush(1.0);
                            if ((incrDecrMask & Node.DECR_FLAG) == 0) {
                                cfw.add(ByteCode.DADD);
                            } else {
                                cfw.add(ByteCode.DSUB);
                            }
                            addDoubleWrap();
                        }
                    }
                    break;
                }
                if (node.getIntProp(Node.ISNUMBER_PROP, -1) != -1) {
                    int offset = varIsDirectCallParameter(varIndex) ? 1 : 0;
                    cfw.addDLoad(reg + offset);
                    if (post) {
                        cfw.add(ByteCode.DUP2);
                    }
                    cfw.addPush(1.0);
                    if ((incrDecrMask & Node.DECR_FLAG) == 0) {
                        cfw.add(ByteCode.DADD);
                    } else {
                        cfw.add(ByteCode.DSUB);
                    }
                    if (!post) {
                        cfw.add(ByteCode.DUP2);
                    }
                    cfw.addDStore(reg + offset);
                } else {
                    if (varIsDirectCallParameter(varIndex)) {
                        dcpLoadAsObject(reg);
                    } else {
                        cfw.addALoad(reg);
                    }
                    addObjectToDouble();
                    if (post) {
                        cfw.add(ByteCode.DUP2);
                    }
                    cfw.addPush(1.0);
                    if ((incrDecrMask & Node.DECR_FLAG) == 0) {
                        cfw.add(ByteCode.DADD);
                    } else {
                        cfw.add(ByteCode.DSUB);
                    }
                    addDoubleWrap();
                    if (!post) {
                        cfw.add(ByteCode.DUP);
                    }
                    cfw.addAStore(reg);
                    if (post) {
                        addDoubleWrap();
                    }
                }
                break;
            case Token.NAME:
                cfw.addALoad(variableObjectLocal);
                cfw.addPush(child.getString()); // push name
                cfw.addALoad(contextLocal);
                cfw.addPush(incrDecrMask);
                addScriptRuntimeInvoke(
                        "nameIncrDecr",
                        "(Lorg/mozilla/javascript/Scriptable;"
                                + "Ljava/lang/String;"
                                + "Lorg/mozilla/javascript/Context;"
                                + "I)Ljava/lang/Object;");
                break;
            case Token.GETPROPNOWARN:
                throw Kit.codeBug();
            case Token.GETPROP:
                {
                    Node getPropChild = child.getFirstChild();
                    generateExpression(getPropChild, node);
                    generateExpression(getPropChild.getNext(), node);
                    cfw.addALoad(contextLocal);
                    cfw.addALoad(variableObjectLocal);
                    cfw.addPush(incrDecrMask);
                    addScriptRuntimeInvoke(
                            "propIncrDecr",
                            "(Ljava/lang/Object;"
                                    + "Ljava/lang/String;"
                                    + "Lorg/mozilla/javascript/Context;"
                                    + "Lorg/mozilla/javascript/Scriptable;"
                                    + "I)Ljava/lang/Object;");
                    break;
                }
            case Token.GETELEM:
                {
                    Node elemChild = child.getFirstChild();
                    generateExpression(elemChild, node);
                    generateExpression(elemChild.getNext(), node);
                    cfw.addALoad(contextLocal);
                    cfw.addALoad(variableObjectLocal);
                    cfw.addPush(incrDecrMask);
                    if (elemChild.getNext().getIntProp(Node.ISNUMBER_PROP, -1) != -1) {
                        addOptRuntimeInvoke(
                                "elemIncrDecr",
                                "(Ljava/lang/Object;"
                                        + "D"
                                        + "Lorg/mozilla/javascript/Context;"
                                        + "Lorg/mozilla/javascript/Scriptable;"
                                        + "I"
                                        + ")Ljava/lang/Object;");
                    } else {
                        addScriptRuntimeInvoke(
                                "elemIncrDecr",
                                "(Ljava/lang/Object;"
                                        + "Ljava/lang/Object;"
                                        + "Lorg/mozilla/javascript/Context;"
                                        + "Lorg/mozilla/javascript/Scriptable;"
                                        + "I"
                                        + ")Ljava/lang/Object;");
                    }
                    break;
                }
            case Token.GET_REF:
                {
                    Node refChild = child.getFirstChild();
                    generateExpression(refChild, node);
                    cfw.addALoad(contextLocal);
                    cfw.addALoad(variableObjectLocal);
                    cfw.addPush(incrDecrMask);
                    addScriptRuntimeInvoke(
                            "refIncrDecr",
                            "(Lorg/mozilla/javascript/Ref;"
                                    + "Lorg/mozilla/javascript/Context;"
                                    + "Lorg/mozilla/javascript/Scriptable;"
                                    + "I)Ljava/lang/Object;");
                    break;
                }
            default:
                Codegen.badTree();
        }
    }

    // Handles super.x++ and variants thereof. We don't want to create new icode in the interpreter
    // for this edge case, so we will transform this into something like super.x = super.x + 1
    private void visitSuperIncDec(Node node, Node child, int incrDecrMask) {
        Node object = child.getFirstChild();

        // Push the old value on the stack
        generateExpression(object, node); // [super]
        cfw.add(ByteCode.DUP); // [super, super]
        switch (child.getType()) {
            case Token.GETPROP:
                cfw.addALoad(contextLocal);
                cfw.addALoad(variableObjectLocal);
                cfw.addALoad(thisObjLocal);
                cfw.addLoadConstant(0); // no_warn flag
                addDynamicInvoke(
                        "PROP:GETSUPER:" + child.getFirstChild().getNext().getString(),
                        Signatures.PROP_GET_SUPER);
                break;

            case Token.GETELEM:
                generateExpression(object.getNext(), node);
                cfw.addALoad(contextLocal);
                cfw.addALoad(variableObjectLocal);
                cfw.addALoad(thisObjLocal);
                addDynamicInvoke("PROP:GETELEMENTSUPER", Signatures.PROP_GET_ELEMENT_SUPER);
                break;

            default:
                Codegen.badTree();
        }

        // stack: [super, p]
        if ((incrDecrMask & Node.POST_FLAG) != 0) {
            // For postfix we want a copy of the old value on the stack
            cfw.add(ByteCode.SWAP); // [p, super]
            cfw.add(ByteCode.DUP2); // [p, super, p, super]
            cfw.add(ByteCode.POP); // [p, super, p]
        }

        // Increment or decrement the value
        addObjectToDouble(); // Unbox
        cfw.addPush(1.0);
        if ((incrDecrMask & Node.DECR_FLAG) == 0) {
            cfw.add(ByteCode.DADD);
        } else {
            cfw.add(ByteCode.DSUB);
        }
        addDoubleWrap(); // Box back

        // Assign the new value to the property
        // Now stack is for prefix: [super, p+-1] and for postfix: [p, super, p+-1]
        switch (child.getType()) {
            case Token.GETPROP:
                cfw.addALoad(contextLocal);
                cfw.addALoad(variableObjectLocal);
                cfw.addALoad(thisObjLocal);
                addDynamicInvoke(
                        "PROP:SETSUPER:" + child.getFirstChild().getNext().getString(),
                        Signatures.PROP_SET_SUPER);
                break;

            case Token.GETELEM:
                generateExpression(object.getNext(), node); // [..., super, p+-1, elem]
                cfw.add(ByteCode.SWAP); // [..., super, elem, p+-1]
                cfw.addALoad(contextLocal);
                cfw.addALoad(variableObjectLocal);
                cfw.addALoad(thisObjLocal);
                addDynamicInvoke("PROP:SETELEMENTSUPER", Signatures.PROP_SET_ELEMENT_SUPER);
                break;
        }
        // Now stack is for prefix: [p+-1] and for postfix: [p, p+-1]

        // If it was a postfix, just drop the new value
        if ((incrDecrMask & Node.POST_FLAG) != 0) {
            cfw.add(ByteCode.POP);
        }
    }

    private static boolean isArithmeticNode(Node node) {
        int type = node.getType();
        return (type == Token.SUB)
                || (type == Token.MOD)
                || (type == Token.DIV)
                || (type == Token.MUL);
    }

    private void visitArithmetic(Node node, int type, Node child, Node parent) {
        int childNumberFlag = node.getIntProp(Node.ISNUMBER_PROP, -1);
        if (childNumberFlag != -1) {
            generateExpression(child, node);
            generateExpression(child.getNext(), node);

            switch (type) {
                case Token.SUB:
                    cfw.add(ByteCode.DSUB);
                    break;
                case Token.MUL:
                    cfw.add(ByteCode.DMUL);
                    break;
                case Token.DIV:
                    cfw.add(ByteCode.DDIV);
                    break;
                case Token.MOD:
                    cfw.add(ByteCode.DREM);
                    break;
                default:
                    throw Kit.codeBug(Token.typeToName(type));
            }
        } else {
            generateExpression(child, node);
            generateExpression(child.getNext(), node);
            if (!isArithmeticNode(child)) {
                cfw.add(ByteCode.SWAP);
                addObjectToNumeric();
                cfw.add(ByteCode.SWAP);
            }
            if (!isArithmeticNode(child.getNext())) addObjectToNumeric();

            switch (type) {
                case Token.SUB:
                    addScriptRuntimeInvoke(
                            "subtract", "(Ljava/lang/Number;Ljava/lang/Number;)Ljava/lang/Number;");
                    break;
                case Token.MUL:
                    addScriptRuntimeInvoke(
                            "multiply", "(Ljava/lang/Number;Ljava/lang/Number;)Ljava/lang/Number;");
                    break;
                case Token.DIV:
                    addScriptRuntimeInvoke(
                            "divide", "(Ljava/lang/Number;Ljava/lang/Number;)Ljava/lang/Number;");
                    break;
                case Token.MOD:
                    addScriptRuntimeInvoke(
                            "remainder",
                            "(Ljava/lang/Number;Ljava/lang/Number;)Ljava/lang/Number;");
                    break;
                default:
                    throw Kit.codeBug(Token.typeToName(type));
            }
        }
    }

    private void visitExponentiation(Node node, Node child, Node parent) {
        int childNumberFlag = node.getIntProp(Node.ISNUMBER_PROP, -1);
        if (childNumberFlag != -1) {
            generateExpression(child, node);
            generateExpression(child.getNext(), node);
            cfw.addInvoke(ByteCode.INVOKESTATIC, "java/lang/Math", "pow", "(DD)D");
        } else {
            generateExpression(child, node);
            generateExpression(child.getNext(), node);

            short reg = getNewWordLocal();
            cfw.addAStore(reg);
            addObjectToNumeric();
            cfw.addALoad(reg);
            addObjectToNumeric();

            addScriptRuntimeInvoke(
                    "exponentiate", "(Ljava/lang/Number;Ljava/lang/Number;)Ljava/lang/Number;");
        }
    }

    private void visitBitNot(Node node, Node child) {
        int childNumberFlag = node.getIntProp(Node.ISNUMBER_PROP, -1);
        generateExpression(child, node);
        if (childNumberFlag == -1) {
            addObjectToNumeric();
            addScriptRuntimeInvoke("bitwiseNOT", "(Ljava/lang/Number;)Ljava/lang/Number;");
        } else {
            addScriptRuntimeInvoke("toInt32", "(D)I");
            cfw.addPush(-1); // implement ~a as (a ^ -1)
            cfw.add(ByteCode.IXOR);
            cfw.add(ByteCode.I2D);
        }
    }

    private void visitBitOp(Node node, int type, Node child) {
        int childNumberFlag = node.getIntProp(Node.ISNUMBER_PROP, -1);
        generateExpression(child, node);

        // special-case URSH; work with the target arg as a long, so
        // that we can return a 32-bit unsigned value, and call
        // toUint32 instead of toInt32.
        if (type == Token.URSH) {
            generateExpression(child.getNext(), node);
            cfw.add(ByteCode.SWAP);
            addDynamicInvoke("MATH:TOUINT32", Signatures.MATH_TO_UINT32);
            cfw.add(ByteCode.DUP2_X1);
            cfw.add(ByteCode.POP2);
            addDynamicInvoke("MATH:TOINT32", Signatures.MATH_TO_INT32);
            // Looks like we need to explicitly mask the shift to 5 bits -
            // LUSHR takes 6 bits.
            cfw.addPush(31);
            cfw.add(ByteCode.IAND);
            cfw.add(ByteCode.LUSHR);
            cfw.add(ByteCode.L2D);
            addDoubleWrap();
            return;
        }
        if (childNumberFlag == -1) {
            generateExpression(child.getNext(), node);
            cfw.add(ByteCode.SWAP);
            addObjectToNumeric();
            cfw.add(ByteCode.SWAP);
            addObjectToNumeric();

            switch (type) {
                case Token.BITOR:
                    addScriptRuntimeInvoke(
                            "bitwiseOR",
                            "(Ljava/lang/Number;Ljava/lang/Number;)Ljava/lang/Number;");
                    break;
                case Token.BITXOR:
                    addScriptRuntimeInvoke(
                            "bitwiseXOR",
                            "(Ljava/lang/Number;Ljava/lang/Number;)Ljava/lang/Number;");
                    break;
                case Token.BITAND:
                    addScriptRuntimeInvoke(
                            "bitwiseAND",
                            "(Ljava/lang/Number;Ljava/lang/Number;)Ljava/lang/Number;");
                    break;
                case Token.RSH:
                    addScriptRuntimeInvoke(
                            "signedRightShift",
                            "(Ljava/lang/Number;Ljava/lang/Number;)Ljava/lang/Number;");
                    break;
                case Token.LSH:
                    addScriptRuntimeInvoke(
                            "leftShift",
                            "(Ljava/lang/Number;Ljava/lang/Number;)Ljava/lang/Number;");
                    break;
                default:
                    throw Kit.codeBug(Token.typeToName(type));
            }
        } else {
            addScriptRuntimeInvoke("toInt32", "(D)I");
            generateExpression(child.getNext(), node);
            addScriptRuntimeInvoke("toInt32", "(D)I");

            switch (type) {
                case Token.BITOR:
                    cfw.add(ByteCode.IOR);
                    break;
                case Token.BITXOR:
                    cfw.add(ByteCode.IXOR);
                    break;
                case Token.BITAND:
                    cfw.add(ByteCode.IAND);
                    break;
                case Token.RSH:
                    cfw.add(ByteCode.ISHR);
                    break;
                case Token.LSH:
                    cfw.add(ByteCode.ISHL);
                    break;
                default:
                    throw Kit.codeBug(Token.typeToName(type));
            }
            cfw.add(ByteCode.I2D);
        }
    }

    private int nodeIsDirectCallParameter(Node node) {
        if (node.getType() == Token.GETVAR && inDirectCallFunction && !itsForcedObjectParameters) {
            int varIndex = fnCurrent.getVarIndex(node);
            if (fnCurrent.isParameter(varIndex)) {
                return varRegisters[varIndex];
            }
        }
        return -1;
    }

    private boolean varIsDirectCallParameter(int varIndex) {
        return fnCurrent.isParameter(varIndex)
                && inDirectCallFunction
                && !itsForcedObjectParameters;
    }

    private void genSimpleCompare(int type, int trueGOTO, int falseGOTO) {
        if (trueGOTO == -1) throw Codegen.badTree();
        switch (type) {
            case Token.LE:
                cfw.add(ByteCode.DCMPG);
                cfw.add(ByteCode.IFLE, trueGOTO);
                break;
            case Token.GE:
                cfw.add(ByteCode.DCMPL);
                cfw.add(ByteCode.IFGE, trueGOTO);
                break;
            case Token.LT:
                cfw.add(ByteCode.DCMPG);
                cfw.add(ByteCode.IFLT, trueGOTO);
                break;
            case Token.GT:
                cfw.add(ByteCode.DCMPL);
                cfw.add(ByteCode.IFGT, trueGOTO);
                break;
            default:
                throw Codegen.badTree();
        }
        if (falseGOTO != -1) cfw.add(ByteCode.GOTO, falseGOTO);
    }

    private void visitIfJumpRelOp(Node node, Node child, int trueGOTO, int falseGOTO) {
        if (trueGOTO == -1 || falseGOTO == -1) throw Codegen.badTree();
        int type = node.getType();
        Node rChild = child.getNext();
        if (type == Token.INSTANCEOF || type == Token.IN) {
            generateExpression(child, node);
            generateExpression(rChild, node);
            cfw.addALoad(contextLocal);
            addScriptRuntimeInvoke(
                    (type == Token.INSTANCEOF) ? "instanceOf" : "in",
                    "(Ljava/lang/Object;"
                            + "Ljava/lang/Object;"
                            + "Lorg/mozilla/javascript/Context;"
                            + ")Z");
            cfw.add(ByteCode.IFNE, trueGOTO);
            cfw.add(ByteCode.GOTO, falseGOTO);
            return;
        }
        int childNumberFlag = node.getIntProp(Node.ISNUMBER_PROP, -1);
        int left_dcp_register = nodeIsDirectCallParameter(child);
        int right_dcp_register = nodeIsDirectCallParameter(rChild);
        if (childNumberFlag != -1) {
            // Force numeric context on both parameters and optimize
            // direct call case as Optimizer currently does not handle it

            if (childNumberFlag != Node.RIGHT) {
                // Left already has number content
                generateExpression(child, node);
            } else if (left_dcp_register != -1) {
                dcpLoadAsNumber(left_dcp_register);
            } else {
                generateExpression(child, node);
                addObjectToDouble();
            }

            if (childNumberFlag != Node.LEFT) {
                // Right already has number content
                generateExpression(rChild, node);
            } else if (right_dcp_register != -1) {
                dcpLoadAsNumber(right_dcp_register);
            } else {
                generateExpression(rChild, node);
                addObjectToDouble();
            }

            genSimpleCompare(type, trueGOTO, falseGOTO);

        } else {
            if (left_dcp_register != -1 && right_dcp_register != -1) {
                // Generate code to dynamically check for number content
                // if both operands are dcp
                int stack = cfw.getStackTop();
                int leftIsNotNumber = cfw.acquireLabel();
                cfw.addALoad(left_dcp_register);
                cfw.add(ByteCode.GETSTATIC, "java/lang/Void", "TYPE", "Ljava/lang/Class;");
                cfw.add(ByteCode.IF_ACMPNE, leftIsNotNumber);
                cfw.addDLoad(left_dcp_register + 1);
                dcpLoadAsNumber(right_dcp_register);
                genSimpleCompare(type, trueGOTO, falseGOTO);
                if (stack != cfw.getStackTop()) throw Codegen.badTree();

                cfw.markLabel(leftIsNotNumber);
                int rightIsNotNumber = cfw.acquireLabel();
                cfw.addALoad(right_dcp_register);
                cfw.add(ByteCode.GETSTATIC, "java/lang/Void", "TYPE", "Ljava/lang/Class;");
                cfw.add(ByteCode.IF_ACMPNE, rightIsNotNumber);
                cfw.addALoad(left_dcp_register);
                addObjectToDouble();
                cfw.addDLoad(right_dcp_register + 1);
                genSimpleCompare(type, trueGOTO, falseGOTO);
                if (stack != cfw.getStackTop()) throw Codegen.badTree();

                cfw.markLabel(rightIsNotNumber);
                // Load both register as objects to call generic cmp_*
                cfw.addALoad(left_dcp_register);
                cfw.addALoad(right_dcp_register);

            } else {
                generateExpression(child, node);
                generateExpression(rChild, node);
            }

            String compareOp;
            switch (type) {
                case Token.GT:
                    compareOp = "MATH:COMPAREGT";
                    break;
                case Token.LT:
                    compareOp = "MATH:COMPARELT";
                    break;
                case Token.GE:
                    compareOp = "MATH:COMPAREGE";
                    break;
                case Token.LE:
                    compareOp = "MATH:COMPARELE";
                    break;
                default:
                    throw Kit.codeBug();
            }

            addDynamicInvoke(compareOp, Signatures.MATH_COMPARE);
            cfw.add(ByteCode.IFNE, trueGOTO);
            cfw.add(ByteCode.GOTO, falseGOTO);
        }
    }

    private void visitIfJumpEqOp(Node node, Node child, int trueGOTO, int falseGOTO) {
        if (trueGOTO == -1 || falseGOTO == -1) throw Codegen.badTree();

        int stackInitial = cfw.getStackTop();
        int type = node.getType();
        Node rChild = child.getNext();

        // Optimize if one of operands is null; but we can't do this
        // for EQ/NEQ because a ScripableObject might overwrite equivalentValues()
        if (type != Token.EQ
                && type != Token.NE
                && (child.getType() == Token.NULL || rChild.getType() == Token.NULL)) {
            // eq is symmetric in this case
            if (child.getType() == Token.NULL) {
                child = rChild;
            }
            generateExpression(child, node);
            int testCode = (type == Token.SHEQ) ? ByteCode.IFNULL : ByteCode.IFNONNULL;
            cfw.add(testCode, trueGOTO);
            cfw.add(ByteCode.GOTO, falseGOTO);
        } else {
            int child_dcp_register = nodeIsDirectCallParameter(child);
            if (child_dcp_register != -1 && rChild.getType() == Token.TO_OBJECT) {
                Node convertChild = rChild.getFirstChild();
                if (convertChild.getType() == Token.NUMBER) {
                    cfw.addALoad(child_dcp_register);
                    cfw.add(ByteCode.GETSTATIC, "java/lang/Void", "TYPE", "Ljava/lang/Class;");
                    int notNumbersLabel = cfw.acquireLabel();
                    cfw.add(ByteCode.IF_ACMPNE, notNumbersLabel);
                    cfw.addDLoad(child_dcp_register + 1);
                    cfw.addPush(convertChild.getDouble());
                    cfw.add(ByteCode.DCMPL);
                    if (type == Token.EQ) cfw.add(ByteCode.IFEQ, trueGOTO);
                    else cfw.add(ByteCode.IFNE, trueGOTO);
                    cfw.add(ByteCode.GOTO, falseGOTO);
                    cfw.markLabel(notNumbersLabel);
                    // fall thru into generic handling
                }
            }

            generateExpression(child, node);
            generateExpression(rChild, node);

            String name;
            int testCode;
            switch (type) {
                case Token.EQ:
                    name = "MATH:EQ";
                    testCode = ByteCode.IFNE;
                    break;
                case Token.NE:
                    name = "MATH:EQ";
                    testCode = ByteCode.IFEQ;
                    break;
                case Token.SHEQ:
                    name = "MATH:SHALLOWEQ";
                    testCode = ByteCode.IFNE;
                    break;
                case Token.SHNE:
                    name = "MATH:SHALLOWEQ";
                    testCode = ByteCode.IFEQ;
                    break;
                default:
                    throw Codegen.badTree();
            }
            addDynamicInvoke(name, Signatures.MATH_EQ);
            cfw.add(testCode, trueGOTO);
            cfw.add(ByteCode.GOTO, falseGOTO);
        }
        if (stackInitial != cfw.getStackTop()) throw Codegen.badTree();
    }

    private void visitSetName(Node node, Node child) {
        String name = node.getFirstChild().getString();
        while (child != null) {
            generateExpression(child, node);
            child = child.getNext();
        }
        cfw.addALoad(contextLocal);
        cfw.addALoad(variableObjectLocal);
        addDynamicInvoke("NAME:SET:" + name, Signatures.NAME_SET);
    }

    private void visitStrictSetName(Node node, Node child) {
        String name = node.getFirstChild().getString();
        while (child != null) {
            generateExpression(child, node);
            child = child.getNext();
        }
        cfw.addALoad(contextLocal);
        cfw.addALoad(variableObjectLocal);
        addDynamicInvoke("NAME:SETSTRICT:" + name, Signatures.NAME_SET_STRICT);
    }

    private void visitSetConst(Node node, Node child) {
        String name = node.getFirstChild().getString();
        while (child != null) {
            generateExpression(child, node);
            child = child.getNext();
        }
        cfw.addALoad(contextLocal);
        addDynamicInvoke("NAME:SETCONST:" + name, Signatures.NAME_SET_CONST);
    }

    private void visitGetVar(Node node) {
        if (!hasVarsInRegs) Kit.codeBug();
        int varIndex = fnCurrent.getVarIndex(node);
        int reg = varRegisters[varIndex];
        if (varIsDirectCallParameter(varIndex)) {
            // Remember that here the isNumber flag means that we
            // want to use the incoming parameter in a Number
            // context, so test the object type and convert the
            //  value as necessary.
            if (node.getIntProp(Node.ISNUMBER_PROP, -1) != -1) {
                dcpLoadAsNumber(reg);
            } else {
                dcpLoadAsObject(reg);
            }
        } else if (fnCurrent.isNumberVar(varIndex)) {
            cfw.addDLoad(reg);
        } else {
            cfw.addALoad(reg);
        }
    }

    private void visitSetVar(Node node, Node child, boolean needValue) {
        if (!hasVarsInRegs) Kit.codeBug();
        int varIndex = fnCurrent.getVarIndex(node);
        generateExpression(child.getNext(), node);
        boolean isNumber = (node.getIntProp(Node.ISNUMBER_PROP, -1) != -1);
        int reg = varRegisters[varIndex];
        boolean[] constDeclarations = fnCurrent.fnode.getParamAndVarConst();
        if (constDeclarations[varIndex]) {
            if (!needValue) {
                if (isNumber) cfw.add(ByteCode.POP2);
                else cfw.add(ByteCode.POP);
            }
        } else if (varIsDirectCallParameter(varIndex)) {
            if (isNumber) {
                if (needValue) cfw.add(ByteCode.DUP2);
                cfw.addALoad(reg);
                cfw.add(ByteCode.GETSTATIC, "java/lang/Void", "TYPE", "Ljava/lang/Class;");
                int isNumberLabel = cfw.acquireLabel();
                int beyond = cfw.acquireLabel();
                cfw.add(ByteCode.IF_ACMPEQ, isNumberLabel);
                int stack = cfw.getStackTop();
                addDoubleWrap();
                cfw.addAStore(reg);
                cfw.add(ByteCode.GOTO, beyond);
                cfw.markLabel(isNumberLabel, stack);
                cfw.addDStore(reg + 1);
                cfw.markLabel(beyond);
            } else {
                if (needValue) cfw.add(ByteCode.DUP);
                cfw.addAStore(reg);
            }
        } else {
            boolean isNumberVar = fnCurrent.isNumberVar(varIndex);
            if (isNumber) {
                if (isNumberVar) {
                    cfw.addDStore(reg);
                    if (needValue) cfw.addDLoad(reg);
                } else {
                    if (needValue) cfw.add(ByteCode.DUP2);
                    // Cannot save number in variable since !isNumberVar,
                    // so convert to object
                    addDoubleWrap();
                    cfw.addAStore(reg);
                }
            } else {
                if (isNumberVar) Kit.codeBug();
                cfw.addAStore(reg);
                if (needValue) cfw.addALoad(reg);
            }
        }
    }

    private void visitSetConstVar(Node node, Node child, boolean needValue) {
        if (!hasVarsInRegs) Kit.codeBug();
        int varIndex = fnCurrent.getVarIndex(node);
        generateExpression(child.getNext(), node);
        boolean isNumber = (node.getIntProp(Node.ISNUMBER_PROP, -1) != -1);
        int reg = varRegisters[varIndex];
        int beyond = cfw.acquireLabel();
        int noAssign = cfw.acquireLabel();
        if (isNumber) {
            cfw.addILoad(reg + 2);
            cfw.add(ByteCode.IFNE, noAssign);
            int stack = cfw.getStackTop();
            cfw.addPush(1);
            cfw.addIStore(reg + 2);
            cfw.addDStore(reg);
            if (needValue) {
                cfw.addDLoad(reg);
                cfw.markLabel(noAssign, stack);
            } else {
                cfw.add(ByteCode.GOTO, beyond);
                cfw.markLabel(noAssign, stack);
                cfw.add(ByteCode.POP2);
            }
        } else {
            cfw.addILoad(reg + 1);
            cfw.add(ByteCode.IFNE, noAssign);
            int stack = cfw.getStackTop();
            cfw.addPush(1);
            cfw.addIStore(reg + 1);
            cfw.addAStore(reg);
            if (needValue) {
                cfw.addALoad(reg);
                cfw.markLabel(noAssign, stack);
            } else {
                cfw.add(ByteCode.GOTO, beyond);
                cfw.markLabel(noAssign, stack);
                cfw.add(ByteCode.POP);
            }
        }
        cfw.markLabel(beyond);
    }

    private void visitGetProp(Node node, Node child) {
        generateExpression(child, node); // object
        if (node.getIntProp(Node.OPTIONAL_CHAINING, 0) == 1) {
            int getExpr = cfw.acquireLabel();
            int after = cfw.acquireLabel();

            cfw.add(ByteCode.DUP);
            addOptRuntimeInvoke("isNullOrUndefined", "(Ljava/lang/Object;)Z");
            cfw.add(ByteCode.IFEQ, getExpr);

            cfw.add(ByteCode.POP);
            Codegen.pushUndefined(cfw);
            cfw.add(ByteCode.GOTO, after);

            cfw.markLabel(getExpr);
            finishGetPropGeneration(node, child.getNext());
            cfw.markLabel(after);
        } else {
            finishGetPropGeneration(node, child.getNext());
        }
    }

    private void finishGetPropGeneration(Node node, Node nameChild) {
        cfw.addALoad(contextLocal);
        cfw.addALoad(variableObjectLocal);

        if (node.getIntProp(Node.SUPER_PROPERTY_ACCESS, 0) == 1) {
            cfw.addALoad(thisObjLocal);
            cfw.addLoadConstant(node.getType() == Token.GETPROPNOWARN ? 1 : 0);
            addDynamicInvoke("PROP:GETSUPER:" + nameChild.getString(), Signatures.PROP_GET_SUPER);
        } else if (node.getType() == Token.GETPROPNOWARN) {
            addDynamicInvoke("PROP:GETNOWARN:" + nameChild.getString(), Signatures.PROP_GET_NOWARN);
        } else {
            addDynamicInvoke("PROP:GET:" + nameChild.getString(), Signatures.PROP_GET);
        }
    }

    private void visitSetProp(int type, Node node, Node child) {
        generateExpression(child, node);
        child = child.getNext();
        Node nameChild = child;
        if (type == Token.SETPROP_OP) {
            cfw.add(ByteCode.DUP);
            finishGetPropGeneration(node, nameChild);
        }
        child = child.getNext();
        generateExpression(child, node);
        cfw.addALoad(contextLocal);
        cfw.addALoad(variableObjectLocal);
        if (node.getIntProp(Node.SUPER_PROPERTY_ACCESS, 0) == 1) {
            cfw.addALoad(thisObjLocal);
            addDynamicInvoke("PROP:SETSUPER:" + nameChild.getString(), Signatures.PROP_SET_SUPER);
        } else {
            addDynamicInvoke("PROP:SET:" + nameChild.getString(), Signatures.PROP_SET);
        }
    }

    private void visitSetElem(int type, Node node, Node child) {
        generateExpression(child, node);
        child = child.getNext();
        if (type == Token.SETELEM_OP) {
            cfw.add(ByteCode.DUP);
        }
        generateExpression(child, node);
        child = child.getNext();
        boolean indexIsNumber = (node.getIntProp(Node.ISNUMBER_PROP, -1) != -1);
        boolean isSuper = node.getIntProp(Node.SUPER_PROPERTY_ACCESS, 0) == 1;
        if (type == Token.SETELEM_OP) {
            if (isSuper) {
                // indexIsNumber will never be true because functions with super always require
                // activation, and when they do we not optimize them

                // stack: ... object object indexObject
                //        -> ... object indexObject object indexObject
                cfw.add(ByteCode.DUP_X1);
                cfw.addALoad(contextLocal);
                cfw.addALoad(variableObjectLocal);
                cfw.addALoad(thisObjLocal);
                addDynamicInvoke("PROP:GETELEMENTSUPER", Signatures.PROP_GET_ELEMENT_SUPER);
            } else if (indexIsNumber) {
                // stack: ... object object number
                //        -> ... object number object number
                cfw.add(ByteCode.DUP2_X1);
                cfw.addALoad(contextLocal);
                cfw.addALoad(variableObjectLocal);
                addDynamicInvoke("PROP:GETINDEX", Signatures.PROP_GET_INDEX);
            } else {
                // stack: ... object object indexObject
                //        -> ... object indexObject object indexObject
                cfw.add(ByteCode.DUP_X1);
                cfw.addALoad(contextLocal);
                cfw.addALoad(variableObjectLocal);
                addDynamicInvoke("PROP:GETELEMENT", Signatures.PROP_GET_ELEMENT);
            }
        }
        generateExpression(child, node);
        cfw.addALoad(contextLocal);
        cfw.addALoad(variableObjectLocal);
        if (isSuper) {
            // Again, we will never have super && indexIsNumber together
            cfw.addALoad(thisObjLocal);
            addDynamicInvoke("PROP:SETELEMENTSUPER", Signatures.PROP_SET_ELEMENT_SUPER);
        } else if (indexIsNumber) {
            addDynamicInvoke("PROP:SETINDEX", Signatures.PROP_SET_INDEX);
        } else {
            addDynamicInvoke("PROP:SETELEMENT", Signatures.PROP_SET_ELEMENT);
        }
    }

    private void visitDotQuery(Node node, Node child) {
        updateLineNumber(node);
        generateExpression(child, node);
        cfw.addALoad(variableObjectLocal);
        addScriptRuntimeInvoke(
                "enterDotQuery",
                "(Ljava/lang/Object;"
                        + "Lorg/mozilla/javascript/Scriptable;"
                        + ")Lorg/mozilla/javascript/Scriptable;");
        cfw.addAStore(variableObjectLocal);

        // add push null/pop with label in between to simplify code for loop
        // continue when it is necessary to pop the null result from
        // updateDotQuery
        cfw.add(ByteCode.ACONST_NULL);
        int queryLoopStart = cfw.acquireLabel();
        cfw.markLabel(queryLoopStart); // loop continue jumps here
        cfw.add(ByteCode.POP);

        generateExpression(child.getNext(), node);
        addDynamicInvoke("MATH:TOBOOLEAN", Signatures.MATH_TO_BOOLEAN);
        cfw.addALoad(variableObjectLocal);
        addScriptRuntimeInvoke(
                "updateDotQuery",
                "(Z" + "Lorg/mozilla/javascript/Scriptable;" + ")Ljava/lang/Object;");
        cfw.add(ByteCode.DUP);
        cfw.add(ByteCode.IFNULL, queryLoopStart);
        // stack: ... non_null_result_of_updateDotQuery
        cfw.addALoad(variableObjectLocal);
        addScriptRuntimeInvoke(
                "leaveDotQuery",
                "(Lorg/mozilla/javascript/Scriptable;" + ")Lorg/mozilla/javascript/Scriptable;");
        cfw.addAStore(variableObjectLocal);
    }

    private static int getLocalBlockRegister(Node node) {
        Node localBlock = (Node) node.getProp(Node.LOCAL_BLOCK_PROP);
        int localSlot = localBlock.getExistingIntProp(Node.LOCAL_PROP);
        return localSlot;
    }

    private void dcpLoadAsNumber(int dcp_register) {
        cfw.addALoad(dcp_register);
        cfw.add(ByteCode.GETSTATIC, "java/lang/Void", "TYPE", "Ljava/lang/Class;");
        int isNumberLabel = cfw.acquireLabel();
        cfw.add(ByteCode.IF_ACMPEQ, isNumberLabel);
        int stack = cfw.getStackTop();
        cfw.addALoad(dcp_register);
        addObjectToDouble();
        int beyond = cfw.acquireLabel();
        cfw.add(ByteCode.GOTO, beyond);
        cfw.markLabel(isNumberLabel, stack);
        cfw.addDLoad(dcp_register + 1);
        cfw.markLabel(beyond);
    }

    private void dcpLoadAsObject(int dcp_register) {
        cfw.addALoad(dcp_register);
        cfw.add(ByteCode.GETSTATIC, "java/lang/Void", "TYPE", "Ljava/lang/Class;");
        int isNumberLabel = cfw.acquireLabel();
        cfw.add(ByteCode.IF_ACMPEQ, isNumberLabel);
        int stack = cfw.getStackTop();
        cfw.addALoad(dcp_register);
        int beyond = cfw.acquireLabel();
        cfw.add(ByteCode.GOTO, beyond);
        cfw.markLabel(isNumberLabel, stack);
        cfw.addDLoad(dcp_register + 1);
        addDoubleWrap();
        cfw.markLabel(beyond);
    }

    private void addGoto(Node target, int jumpcode) {
        int targetLabel = getTargetLabel(target);
        cfw.add(jumpcode, targetLabel);
    }

    private void addObjectToDouble() {
        addDynamicInvoke("MATH:TONUMBER", Signatures.MATH_TO_NUMBER);
    }

    private void addObjectToNumeric() {
        addDynamicInvoke("MATH:TONUMERIC", Signatures.MATH_TO_NUMERIC);
    }

    private void addNewObjectArray(int size) {
        if (size == 0) {
            if (itsZeroArgArray >= 0) {
                cfw.addALoad(itsZeroArgArray);
            } else {
                cfw.add(
                        ByteCode.GETSTATIC,
                        "org/mozilla/javascript/ScriptRuntime",
                        "emptyArgs",
                        "[Ljava/lang/Object;");
            }
        } else {
            cfw.addPush(size);
            cfw.add(ByteCode.ANEWARRAY, "java/lang/Object");
        }
    }

    private void addScriptRuntimeInvoke(String methodName, String methodSignature) {
        cfw.addInvoke(
                ByteCode.INVOKESTATIC,
                "org.mozilla.javascript.ScriptRuntime",
                methodName,
                methodSignature);
    }

    private void addOptRuntimeInvoke(String methodName, String methodSignature) {
        cfw.addInvoke(
                ByteCode.INVOKESTATIC,
                "org/mozilla/javascript/optimizer/OptRuntime",
                methodName,
                methodSignature);
    }

    private void addDynamicInvoke(String operation, String signature) {
        cfw.addInvokeDynamic(operation, signature, Bootstrapper.BOOTSTRAP_HANDLE);
    }

    private void addJumpedBooleanWrap(int trueLabel, int falseLabel) {
        cfw.markLabel(falseLabel);
        int skip = cfw.acquireLabel();
        cfw.add(ByteCode.GETSTATIC, "java/lang/Boolean", "FALSE", "Ljava/lang/Boolean;");
        cfw.add(ByteCode.GOTO, skip);
        cfw.markLabel(trueLabel);
        cfw.add(ByteCode.GETSTATIC, "java/lang/Boolean", "TRUE", "Ljava/lang/Boolean;");
        cfw.markLabel(skip);
        cfw.adjustStackTop(-1); // only have 1 of true/false
    }

    private void addDoubleWrap() {
        addOptRuntimeInvoke("wrapDouble", "(D)Ljava/lang/Double;");
    }

    /**
     * Const locals use an extra slot to hold the has-been-assigned-once flag at runtime.
     *
     * @param isConst true iff the variable is const
     * @return the register for the word pair (double/long)
     */
    private short getNewWordPairLocal(boolean isConst) {
        return getNewWordIntern(isConst ? 3 : 2);
    }

    private short getNewWordLocal(boolean isConst) {
        return getNewWordIntern(isConst ? 2 : 1);
    }

    private short getNewWordLocal() {
        return getNewWordIntern(1);
    }

    private short getNewWordIntern(int count) {
        assert count >= 1 && count <= 3;

        int[] locals = this.locals;
        int result = -1;
        if (count > 1) {
            // we need 'count' consecutive free slots
            OUTER:
            for (int i = firstFreeLocal; i + count <= MAX_LOCALS; ) {
                for (int j = 0; j < count; ++j) {
                    if (locals[i + j] != 0) {
                        i += j + 1;
                        continue OUTER;
                    }
                }
                result = i;
                break;
            }
        } else {
            result = firstFreeLocal;
        }

        if (result != -1) {
            locals[result] = 1;
            if (count > 1) locals[result + 1] = 1;
            if (count > 2) locals[result + 2] = 1;

            if (result == firstFreeLocal) {
                for (int i = result + count; i < MAX_LOCALS; i++) {
                    if (locals[i] == 0) {
                        firstFreeLocal = (short) i;
                        if (localsMax < firstFreeLocal) localsMax = firstFreeLocal;
                        return (short) result;
                    }
                }
            } else {
                return (short) result;
            }
        }

        throw Context.reportRuntimeError("Program too complex (out of locals)");
    }

    // This is a valid call only for a local that is allocated by default.
    private void incReferenceWordLocal(int local) {
        locals[local]++;
    }

    // This is a valid call only for a local that is allocated by default.
    private void decReferenceWordLocal(int local) {
        locals[local]--;
    }

    private void releaseWordLocal(int local) {
        if (local < firstFreeLocal) firstFreeLocal = local;
        locals[local] = 0;
    }

    static final int GENERATOR_TERMINATE = -1;
    static final int GENERATOR_START = 0;
    static final int GENERATOR_YIELD_START = 1;

    ClassFileWriter cfw;
    Codegen codegen;
    CompilerEnvirons compilerEnv;
    ScriptNode scriptOrFn;
    String scriptOrFnType;
    String scriptOrFnClass;
    public int scriptOrFnIndex;
    private int savedCodeOffset;

    private OptFunctionNode fnCurrent;

    private static final int MAX_LOCALS = 1024;
    private int[] locals;
    private int firstFreeLocal;
    private int localsMax;

    private int itsLineNumber;

    private boolean hasVarsInRegs;
    private int[] varRegisters;
    private boolean inDirectCallFunction;
    private boolean itsForcedObjectParameters;
    private int enterAreaStartLabel;
    private int epilogueLabel;
    private boolean inLocalBlock;

    // special known locals. If you add a new local here, be sure
    // to initialize it to -1 in initBodyGeneration
    private int variableObjectLocal;
    private int popvLocal;
    private int contextLocal;
    private int argsLocal;
    private int operationLocal;
    private int thisObjLocal;
    private int funObjLocal;
    private int itsZeroArgArray;
    private int itsOneArgArray;
    private int generatorStateLocal;
    private int savedHomeObjectLocal;
    private int newTargetLocal;

    private boolean isGenerator;
    private int generatorSwitch;
    private int maxLocals = 0;
    private int maxStack = 0;

    private Map<Node, FinallyReturnPoint> finallys;
    private ArrayList<Node> literals;

    static class FinallyReturnPoint {
        public List<Integer> jsrPoints = new ArrayList<>();
        public int tableLabel = 0;
    }

    private int unnestedYieldCount = 0;
    private IdentityHashMap<Node, String> unnestedYields = new IdentityHashMap<>();
}
